# Signal Through the Noise > Honest takes on code, AI, and what actually works. Essays by Ivan Turković, software architect and engineering leader (20+ years), on AI-driven development, software engineering, Ruby on Rails and engineering careers. Author: Ivan Turković — https://www.linkedin.com/in/ivanturkovic — https://x.com/ithora Available for contract and full-time remote work: https://www.ivanturkovic.com/hire-me/ --- # The Rise of Wasteful Resources URL: https://www.ivanturkovic.com/2026/10/15/ai-waste-nobody-optimizes-anymore/ Published: 2026-10-15 Topics: AI, Software Engineering We used to be proud of making things small. Smaller binaries. Faster queries. Fewer allocations. A page that loaded in under a second on a bad connection. We measured it, argued about it in code review, and bragged about it afterwards. Somewhere in the last two years that pride quietly disappeared. Today the default answer to almost any problem is to throw more at it. More tokens. More context. More agents running in parallel. More regenerated files. More compute that nobody sees and nobody counts. And almost nobody seems to care. Call it what it is: AI waste. We used to brag about bytes The Apollo Guidance Computer flew people to the Moon with roughly 4 KB of erasable memory and about 72 KB of read-only rope memory. Every word of it was argued over. The demoscene built an entire culture around constraints. People wrote complete audiovisual productions that fit in 64 KB, then in 4 KB, just to prove it could be done. Game developers squeezed whole worlds onto cartridges. Database people spent days shaving milliseconds off a query because it ran a million times a day. Frontend engineers fought over every kilobyte of JavaScript because they knew someone on a slow phone was paying for it. None of that was vanity. Optimization was respect. Respect for the machine, for the user, for the person paying the bill, and for the engineer who would maintain the code next year. Doing more with less was the craft. Now the answer is "throw more at it" Look at how a lot of work gets done now. An agent rewrites an entire file to change one line. A whole repository gets pulled into context to fix a typo. Five agents attempt the same task in parallel, four results get thrown away. A language model is called to format a date, parse JSON or check whether a string is empty. Tests fail, so the model tries again. And again. Until something is green. A 300-line abstraction appears where a ten-line function would have done the job. Each of these is small. Each is easy to justify in the moment. Together they describe an engineering culture that has stopped asking the most basic question we used to ask about everything: what is the least this actually needs? I wrote before about 200,000 lines of AI code that disappeared without anything breaking. That was the waste you can see. The waste you cannot see is bigger. Why nobody cares about AI waste It is not that engineers got lazy overnight. The incentives changed. The cost is hidden. A flat monthly subscription makes every extra token feel free. The bill for the compute lands somewhere else, in someone else's data center, on someone else's balance sheet. You never see it, so you never optimize it. We reward output, not efficiency. Teams celebrate how much got shipped, how many pull requests were opened, how many lines were generated. I have argued that lines of code is the wrong way to measure AI productivity. Measuring volume quietly rewards waste. "It is cheaper than my time." Sometimes that is true. Burning a few cents of compute to save an hour of a senior engineer is a good trade. But it has become a reflex instead of a decision. Nobody checks whether it is still true at a thousand requests a minute. The abundance feels permanent. When something has always been available, nobody builds the habit of measuring it. The bill always arrives Waste does not disappear because you cannot see it. It moves. It shows up as latency, when the feature that called a model three times in a row feels sluggish to users. It shows up as cost, the moment a prototype becomes a product and the usage curve goes up. I have seen how much changes when you treat model calls as a budget instead of a reflex, which is why something as simple as caching embeddings can save real money. It shows up as complexity. Generated code that nobody fully understands is code somebody will have to understand later. Complexity is never eliminated, it is only relocated, and waste is one of the places it hides. It shows up outside the codebase too. Every unnecessary call runs on real hardware that needs real electricity and real cooling. At the scale the whole industry now operates, that stops being an abstraction. And the economic side of this shift is bigger than any one team, as I explored in Is AI quietly contracting the world economy? The most expensive cost is the quietest one. A generation of engineers is growing up without ever learning to measure. They have never profiled a slow endpoint, never counted queries on a page, never asked what a feature costs to run. That skill does not come back the day you need it. How to cut AI waste: what optimization looks like now I am not arguing for hand-tuning assembly or refusing to use AI. The tools are genuinely useful. The point is to use them deliberately. Deterministic problems get deterministic code. If a regular expression, a SQL query or a ten-line function solves it, do not call a model. Right-size the model. The biggest model is not the default. Most tasks do not need it. Edit, do not regenerate. Small diffs instead of rewritten files. Narrow context instead of the whole repository. Cache what repeats. Embeddings, summaries, classifications. If you computed it once, keep it. Measure it like we measured queries. Tokens per feature, calls per request, cost per user. Put it on the dashboard next to latency. Ask the old question. Would I write it this way by hand? If not, why is it acceptable when a model does it? None of this is new. It is the same judgment that always separated good engineering from merely working code. It just moved to a different layer, the one I called the middle loop, where engineers decide what the machine should do and check what it actually did. Efficiency is still a craft Constraints made us better engineers. They forced us to understand the problem before we threw resources at it. Abundance does the opposite. It lets us skip the understanding. The best engineers I know still ask the question nobody seems to ask anymore: what is the least this needs? Not because compute is scarce, but because the answer tells you whether you actually understand what you are building. Waste used to be a sign of a junior engineer. Now it is a default setting. It does not have to be. Have you noticed this shift in your team? What is the most wasteful AI pattern you have seen this month? --- # I Still Miss Writing Code URL: https://www.ivanturkovic.com/2026/08/19/still-miss-writing-code/ Published: 2026-08-19 Topics: AI, AI-Driven Development, Career & Leadership, Software Engineering There is a specific kind of quiet I have been thinking about lately. It is late. The office is empty, or the house is finally asleep. There is a problem on the screen that refuses to be solved, and for once there is nothing else. No Slack. No calendar invite. No one asking for an update. Just a cursor, a failing test, and the slow, private work of understanding what is actually going on. I miss that quiet more than I thought I would. Somebody asked me recently whether I missed writing code by hand, the way we used to before the machines started doing so much of it for us. I gave a quick answer at the time. Something practical. Something about leverage and output and how the job was never really about typing. All of that is true, and I have written about it more than once. But it was not an honest answer. The honest answer is yes. I miss it. I miss it in a way that has very little to do with productivity and everything to do with what the work did for me. It started as a puzzle Every real problem was a small mystery. A bug that made no sense. A system behaving in a way the documentation swore was impossible. You could not brute force your way through it. You had to sit with it. You had to hold the whole thing in your head at once: the data flowing one way, the state changing another, the single assumption buried three layers down that turned out to be wrong. There is a particular feeling when the shape of the problem finally clicks into place. It is not loud. It is a quiet click, somewhere behind your eyes, and suddenly the mess resolves into something you can actually see. For a moment you understand a small piece of the world completely. Not partially. Not approximately. Completely. There are not many places in adult life where you get that. The research was half the pleasure Before I wrote a single line, I read. I read the documentation, and then I read the source when the documentation lied. I read old forum threads from 2011 where someone had hit the exact same wall and described it badly, and I read the one reply underneath that quietly solved it. I learned how things actually worked, not how they were supposed to work. That reading was never wasted. Every hour spent understanding a library, a protocol, a strange corner of the language, compounded. You were not just fixing today's problem. You were drawing the map you would use for the next ten years. I still carry maps I sketched fifteen years ago, late at night, one problem at a time. Then came the flow And then the part no one warns you is addictive. You start writing. Test, red. Adjust. Test, red again. A small change. Test, green. Then the next one. The loop is tight and honest and completely yours. The computer does not flatter you and it does not lie. Your code either works or it does not, and when it does not, it is patiently, precisely your fault. Hours would disappear. I would look up and it was dark outside, or light again, and I had not noticed either happen. People talk about flow as though it were a productivity trick. It was never that for me. It was closer to meditation. The world narrowed to one problem, the noise in my head went silent, and for a while there was only the work. I have spent a lot of years since trying to find that same silence in other ways. Very few of them worked as well as a hard problem and an empty evening. The craft was in the final read The part I loved most came at the end. You would finish, and then you would open your own diff and read it like a stranger. Not the author, the reviewer. You would look for the line that was clever instead of clear. The name that made sense at midnight and made none in the morning. The comment that explained what the code did instead of why. You would delete things. You would rename things. You would make it smaller. That final pass was where the craft lived. Anyone can make something work once. Making it simple, making it obvious, making it kind to the next person who would have to read it, that was the real skill. And then the edge cases. Always the edge cases. The empty list. The null that should never happen. The user who would do the one thing you swore no user would ever do. Hunting them down was tedious, and it was also a kind of care. You were quietly promising the future that you had thought about it. And then you were brave Then you did the bravest thing a developer does. You opened the pull request. There is a small, particular anxiety in that moment that anyone who has shipped code will recognize. You have spent hours, sometimes days, on something you have quietly started to think of as a little bit of a masterpiece. It is clean. It is tested. You are proud of it. And now you are about to hand it to people whose entire job, in that moment, is to find everything wrong with it. The comments would come. Sometimes a few. Sometimes far too many. A question about a decision you thought was obvious. A better approach you had not seen. A blunt "why?" underneath the line you were most proud of. It stung every single time, even after twenty years, even when the reviewer was right, and especially when the reviewer was right. But that was the point. The code got better because it was seen. You got better because you were questioned. The masterpiece you walked in with was almost never the thing that shipped, and the thing that shipped was almost always better for it. Being reviewed was humbling in exactly the way that makes people good at their craft. Why it felt like therapy Here is what I understand now that I did not fully understand then. Writing code was one of the few parts of my life with a clean loop. A problem, a real one, with a real answer. Effort that mapped directly onto progress. A moment where the thing did not work, and then a moment, entirely because of you, where it did. Most of adult life does not offer that. The problems that matter most, the ones about people and organizations and health and the future, are ambiguous, slow, and never fully solved. Code gave me a small, honest corner of the world where I could put something right and actually know that I had. That is why it felt meditative. That is why it felt therapeutic. It was not the syntax. It was the wholeness of it. The completeness. The rare, grounding experience of a problem you could actually finish. What I hold onto I do not write much code by hand anymore, and neither do most of the people I work with. I have made my peace with that. The tools are extraordinary, the leverage is real, and I am not interested in pretending the old way was better simply because it was mine. It was not always better. It was often slow, and lonely, and stubborn in ways that helped no one. But something real did go quiet when the typing stopped, and I think it is worth naming instead of waving away. The craft has not disappeared. It has moved. It lives now in the clarity of what you ask for, in the judgment to see when the answer is wrong, in the taste to keep what is good and delete the rest. That is still meditative. That is still a kind of care. It just wears different clothes. Still, every so often, I close everything, open an empty file, and solve something small entirely by hand. No assistant. No shortcut. Just me, the problem, and that old, particular quiet. I do not do it because it is efficient. I do it because it reminds me why I fell in love with this in the first place. Final words If you have been doing this long enough to feel any of that, I would love to hear from you. What is the one late night, the one stubborn bug, the one small masterpiece you still remember solving entirely by hand? You can find me on LinkedIn (linkedin.com/in/ivanturkovic), X (x.com/ithora), and Threads (threads.com/@ithora). If you want to reach me directly, the contact page is at ivanturkovic.com/contact. For hiring or fractional CTO inquiries, you can book a 30-minute call at cal.eu/ivan-turkovic/30min. So tell me: when you open your editor tomorrow morning, is any part of it still fun, or did that quietly become something you used to do? Then tell me what you think. Do you miss writing code too, or are you glad to let it go? I am genuinely curious where you land. --- # The Editor Wars Are Over. The Reading Wars Started. URL: https://www.ivanturkovic.com/2026/07/29/fun-was-load-bearing/ Published: 2026-07-29 Topics: AI, Software Engineering Last Tuesday I opened Neovim at nine and closed it after one. Four hours. I pressed i twice. The rest was j, k, Ctrl-d, /, gd, Ctrl-o, and a lot of sitting still. An agent had written a 380-line change across six files overnight. My job was to find out whether it was right. I threw away two files, kept three, and rewrote one function by hand because the agent had solved a problem we do not have. My config loads forty-one plugins. Maybe six of them mattered that morning. That was the day the joke stopped being a joke. The half of my config that died The snippet engine is dead. I have not expanded a snippet in more than a year and I only noticed when I went looking for something to cut. Autopairs is still installed. I cannot tell you the last time it saved me a keystroke that mattered. Abbreviations, dead. The completion source priority ordering I tuned for a week in 2023, dead, replaced by something that does not care what order my sources are in. The little leader bindings that scaffolded a class or a spec file. All of it, dead. Every one of those is insert mode machinery. What survived is the other list. Telescope. Treesitter text objects. gd, Ctrl-o, Ctrl-i. The quickfix list. :cdo. Diffview. Folds. %. }. Search with n and N until the shape of the thing finally shows up. Not one of those produces a character. All of it is navigation. All of it is reading. Normal mode is the reading mode. It always was. Vim boots into it, lives in it, and makes you ask permission before you are allowed to type. People called that unintuitive for thirty years. It was not unintuitive. It was early. I spent fifteen years tuning the part of the editor I now barely enter. The loop broke The old loop was think, type, run, fix. Tight. Measured in minutes. The new loop is describe, wait, read, keep or throw away. Loose. Sometimes an hour goes by before I touch a key that changes a file. The waiting is the part nobody warned me about. Not because it is long. Because it broke the thing that made editing feel good, which was that the machine answered at the speed of my hands. Now it answers at the speed of a model and my hands have nothing to do. So I filled the gap. Second buffer, second task, second agent running in a split. And that is exactly where it went wrong, because the review that came next was happening in a head that had context-switched four times in ten minutes. The most useful change I made to my workflow this year involved no plugin at all. I stopped running two agents at once. Read the admiration column, not the usage column Stack Overflow asked 26,143 developers about their editors in 2025. Everyone quoted the usage numbers. VS Code at 75.9%. Notepad++ at 27.4%. IntelliJ at 27.1%. Vim at 24.3%. Cursor arriving on the list for the first time at 17.9%. Neovim at 14%. Claude Code at 9.7%. Zed at 7.3%. Nobody quoted the other column. Admiration counts the people who used a tool and want to keep using it, which is a much harder thing to buy. Neovim: 74.4%. Highest in the survey by twelve points. Then RustRover and VS Code at 62.6%, Rider at 60.8%, Vim at 59.3%, WebStorm at 59.1%, IntelliJ at 58.2%, Zed at 57%, Notepad++ at 54.7%, PyCharm at 52.9%. Now the AI-native tools. Claude Code is the best of them at 52.5%. Cursor at 46.7%. Cline and Roo at 44.8%. Windsurf at 41.8%. Lovable at 36.6%. Bolt at 36.4%. Thirteen conventional editors score higher than the best-liked AI-native tool in the survey. Notepad++ beats every single one of them on both columns. A Windows quick-edit utility, more used than Cursor and better liked than Claude Code. Eclipse is the floor of the whole table at 32.9%, and Lovable and Bolt are within four points of Eclipse. The highest-scoring editor with serious AI ambitions is Zed at 57%, and Zed is the one that decided not to own the agent. Hold that thought. The editors that were right early and got laughed at Emacs people solved this fifteen years ago and nobody listened. Magit is the best diff and staging interface anybody has ever built inside an editor, and it has been sitting there longer than most AI companies have existed. Stage a hunk, split it, discard half of it, read the whole change as a structure rather than a wall. That is a reading tool. It was treated as a niche curiosity by an industry that then went out and paid billions for products that do a worse version of it. Stack Overflow moved Emacs and Helix off the preset answer list in 2025 and into write-ins, so their numbers stopped being comparable. Emacs did not decline. It got delisted. JetBrains spent twenty-five years building find usages, call hierarchies, structural search, and inspections while the rest of us made jokes about the splash screen and the RAM. Every one of those features is reading infrastructure. JetBrains then adopted the Agent Client Protocol across the entire suite and co-launched the shared agent registry with Zed in January 2026. On the merits, JetBrains is the best-positioned company in this entire story, and almost nobody is saying so because they are not a startup. Helix made the most honest argument and got ignored. Modal editing, LSP and treesitter built into the binary, and a founding position that you should not have to configure anything. Twenty years of config culture, and the editor that said do not bother is a rounding error in the survey. We did not want a better default. We wanted a project. The bottleneck moved and everybody can see it Faros AI looked at telemetry from more than 10,000 developers across 1,255 teams. Developers using AI completed 21% more tasks and merged 98% more pull requests. Review time per pull request went up 91%. Twice the code. Twice the wait. One 2025 measurement puts senior engineers at 4.3 minutes reviewing an AI suggestion against 1.2 minutes for code a human wrote. METR found that 75% of the developers in its study read every line of AI-generated code. DORA surveyed nearly 5,000 professionals and found 90% adoption with a median of two hours a day spent working with these tools. Then Cursor bought Graphite on December 19, 2025, for well above Graphite's $290 million valuation. Michael Truell's stated reason was that "reviewing code looks the same as it did three years ago," and that review keeps taking a larger share of the week as writing takes less. The company that made writing code fastest went out and bought a code review company. Read that again. This exact thing already happened once 2016. Microsoft publishes the Language Server Protocol. Before it, every editor built its own language support from scratch, and that was the moat. After it, one language server powered VS Code, Vim, Emacs, and Sublime at the same time. That was supposed to be terrible for editors. It flattened the thing they competed on. It was the best thing that ever happened to Neovim. A terminal editor from 2015 got IntelliJ-grade intelligence for the price of a config file, and the entire modern Neovim revival stands on that one decision. Now watch 2025. Zed ships the Agent Client Protocol, modeled openly on LSP. Agents run as subprocesses, JSON-RPC over stdio, editor owns the interface, agent owns the logic. JetBrains adopts it across the whole IDE suite. The two of them launch a shared agent registry in January 2026. avante.nvim speaks it. Microsoft has standardized VS Code on MCP and has not committed. Same movie, second showing. The agent is being pulled out of the editor and turned into a process the editor talks to, which is why Zed scores 57% while owning none of the intelligence. The moat around AI-native editors is dissolving on roughly the same timeline as the moat around language-aware IDEs did, for the same structural reason. Whatever you think Cursor's product is, a good chunk of it becomes a protocol. What the next editor competes on Diff as a first-class object rather than a git plugin bolted to the side. The best diff experience in most editors today is worse than GitHub's web view, which is embarrassing for a tool you live inside eight hours a day. Emacs has had the answer since before any of this. Navigation through code you have never seen and did not write. Different problem from navigating code you wrote last month. JetBrains has a twenty-five year head start and does not seem to know it. Provenance. Which agent, which run, which prompt, which instruction file produced this block. Blame tells you which human touched a line. It tells you nothing about which of the four agents on your team did, or what you told it to do. Neovim 0.12 shipped in March 2026 with a built-in plugin manager and native insert mode completion. Both good. Both the last war. I upgraded on day one and I will keep upgrading, and the core team is still optimizing a mode I visit less every quarter. The fun was load-bearing Nobody learned vim motions because of a spreadsheet. There is no study. Nobody has ever shown that Vim users ship more software than VS Code users, and we never checked, because checking was never the point. We did it because it was fun. That matters more than it sounds. The fun is what made people go deep enough on their tools to become genuinely fluent in them, and fluency is the thing that turned out to be scarce. When an agent drops 380 lines in front of you, the only thing standing between that code and production is whether you understand the system well enough to know it is wrong. Nobody grinds through fifteen years of that on discipline. They do it because they liked it. Take the enjoyment out of the craft and you remove the mechanism that produces the people worth hiring. That is the apprenticeship problem one level down, running inside a single person instead of an org chart. Here is the part that hurts to type. Some of that config time was procrastination with excellent posture. Fun and avoidance felt identical from the inside. I have rewritten a statusline instead of writing a migration and told myself it was investment. Both are true. It was partly wasted and it was still the engine. I still press i. Just less. The half of the editor that made me a typist is finished. The half that made me a reader is what I get paid for. I liked the first half more. Final Words I would like to know if this matches your experience or contradicts it, especially if you left the terminal for an AI-native editor and found the enjoyment came back rather than went away. The post is only half an argument without the pushback. You can find me on LinkedIn (linkedin.com/in/ivanturkovic), X (x.com/ithora), and Threads (threads.com/@ithora). If you want to reach me directly, the contact page is at ivanturkovic.com/contact. For hiring or fractional CTO inquiries, you can book a 30-minute call at cal.eu/ivan-turkovic/30min. So tell me: when you open your editor tomorrow morning, is any part of it still fun, or did that quietly become something you used to do? --- # Do You Still Read Books? URL: https://www.ivanturkovic.com/2026/07/25/do-you-still-read-books-learning-to-code-ai-era/ Published: 2026-07-25 Topics: AI A developer asked me that on a call in June. Not sarcastically. He asked it the way you would ask someone who still develops film. I had mentioned I picked up the second edition of Designing Data-Intensive Applications. Kleppmann and Riccomini shipped it this spring, nine years after the first one. Six hundred and fifty pages. His question was fair. Why sit with that for three weeks when a model summarizes the whole thing in four seconds and then answers follow up questions inside your own codebase? Because the summary was never the point. I keep coming back to this, because it is the same question sitting underneath every argument about learning to code in the AI era. Do the exercises still matter. Does typing it out yourself still matter. Or is all of that just ritual we defend because we suffered through it and want the next people to suffer too. The exercises were never about the answers Nobody ever learned anything from the answer to an exercise. The answer is worthless. You always knew the answer existed somewhere. What you did not have, before you sat down, was twenty minutes of being stuck. The stuck part is the product. Everything else is packaging. There is hard evidence for this now and it is not comfortable. Bastani and colleagues ran a controlled experiment with roughly a thousand high school students in Turkey. Three groups. One worked with a plain GPT-4 chat interface. One worked with a tutor version built with guardrails that refused to hand over answers. One worked with nothing but books and notes. During practice, the plain chat group scored 48 percent higher than the control group. The tutor group scored 127 percent higher. Then the researchers took the AI away and ran an exam. The plain chat group scored 17 percent worse than the students who never had access at all. The tutor group landed exactly where the control group landed. Not better. Not worse. All that assisted brilliance during practice converted to nothing you could carry out of the room. The work was published in PNAS in 2025 and the title is not subtle. Generative AI without guardrails can harm learning. Then there is the MIT Media Lab study. Fifty four people, EEG caps, essay writing across four months. The group writing with ChatGPT showed the weakest neural connectivity of the three conditions. They reported the lowest sense of ownership over their own work. Many of them could not quote a sentence they had submitted minutes earlier. The researchers called it cognitive debt. That is a generous name for it. And then the finding I cannot stop thinking about. Microsoft Research and Carnegie Mellon surveyed 319 knowledge workers about 936 real tasks. The more confidence people had in the AI, the less critical thinking they did. The more confidence people had in their own ability, the more. Your own competence is what keeps you skeptical. Which means the loop closes on itself. Skip the hard part, lose the competence, lose the skepticism, and then stop noticing that you skipped the hard part. So is there still a purpose in learning to write code Yes. For a different reason than the one you were given. You do not learn to write code so you can write code. You learn to write code so you can read it. Reading is the job now. For a lot of senior people I know it has quietly become the entire job. Veracode tested more than a hundred models across eighty coding tasks. Forty five percent of the generated code introduced an OWASP Top 10 vulnerability. Java was worst at 72 percent. Larger models did not fix it. That is not a defect waiting for a patch. That is what you get when you train on the public internet. Someone has to catch that. The someone is you. You cannot review what you cannot read. You cannot smell a bad abstraction you have never built by hand. You cannot look at a query and feel the table scan coming if you have never watched a plan flip under load. That feeling is not knowledge you retrieve. It is knowledge you sedimented. Stack Overflow's 2025 survey says 44 percent of developers now learn to code using AI tools, up from 37 percent the year before. Documentation still leads at 68 percent. Books do not make the headline at all. I am not going to pretend that is a tragedy. Most programming books were bad. Most were syntax manuals with a print run, and syntax manuals deserved to die. Nobody should memorize an API in 2026. But the good books were never syntax manuals. That is the confusion at the center of this. The shelf Here is what I would actually put in front of someone, and I am being deliberately short about it. For architecture, the second edition of Designing Data-Intensive Applications, because it explains why systems fail rather than which library to install. Next to it, Fundamentals of Software Architecture by Richards and Ford, which also got a second edition in 2025, and A Philosophy of Software Design by Ousterhout, which is short enough that there is no excuse. If you run anything in production, Release It! by Nygard is the book that saves you at 3am. Bulkheads, timeouts, circuit breakers. Written before anyone called it resilience engineering. For the craft of the code itself, The Pragmatic Programmer, Fowler's Refactoring, and Working Effectively with Legacy Code by Feathers. That last one is more relevant now, not less, because AI produces legacy code faster than any team in history. Kent Beck's Tidy First? takes ninety minutes and changes how you open a pull request. For infrastructure and operations, the Google SRE book and the SRE Workbook, both free on the web. Accelerate by Forsgren, Humble and Kim for the measurement side. Team Topologies for the shape of the organization, which becomes the shape of your system whether you agree with that or not. For security, Ross Anderson's Security Engineering. Chapters are free on his Cambridge page and it is still the most honest book about the fact that most breaches are not clever. Google's Building Secure and Reliable Systems is also free, and it is the only book I know that treats security and reliability as one discipline instead of two departments. For performance and scaling, Brendan Gregg's Systems Performance. It is dense, you will not read it front to back, and that is fine. Read the methodology chapters, then use the rest when something is burning. Alex Petrov's Database Internals for storage. Roberto Vitillo's Understanding Distributed Systems if DDIA feels too heavy for a first pass. That is the shelf. Not fifty books. Twelve or so, spread across three years. Can you learn infra, security and scaling without going deep into code Partly. And the part you can learn without code has a ceiling that arrives sooner than people expect. You can become genuinely useful in devops without writing application code. Plenty of excellent platform engineers came out of operations. Learn Terraform, Kubernetes, pipelines, alerting, cost control, and you can be very good at all of it. Learn the OWASP Top 10, run the scanners, file the tickets. Then an incident happens. The service is timing out. The dashboard says the database is healthy. Pods are fine. CPU is flat. At that moment the only person who helps is the one who can open the code, find the connection pool configuration, work out why the pool is exhausted while the database sits idle, and notice that the retry logic three layers up is making everything worse. That person is not reading Terraform. They are reading code. Infrastructure, security, scaling, performance. Every one of those is the same question asked from a different angle. What happens when code meets reality. You cannot answer it from outside the code. Here is the part that genuinely got easier. Going deep into code no longer means writing all of it. It means reading it, tracing it, instrumenting it, breaking it on purpose in staging and watching what falls over. Ask the model to explain a subsystem, then verify the explanation against the source. Ask it for three implementations of the same thing and force yourself to argue why one wins. Use the model as the tutor arm of that experiment. Not the answer arm. The gap between those two groups was 17 percent of everything. What I actually do I read the book slowly. I keep a file of things I disagreed with. I run the examples, then run them wrong on purpose, because the failure mode teaches more than the happy path ever did. Then I use AI constantly. Every day. Scaffolding, review, the fifth alternative I would not have reached on my own, the shell incantation I refuse to memorize. Those two things are not in tension. They are the same practice separated by a few years of compounding. The book is slow. That is the feature. Final Words. If you think I am romanticizing paper, tell me. I would rather be argued with than agreed with, and the people who disagree with me on this usually have the more interesting version of the argument. Maybe your experience is that the books were always overrated and the only real teacher was production. That is a defensible position and I have seen it work for people. I write about this every week. You can follow along on LinkedIn (linkedin.com/in/ivanturkovic), on X (x.com/ithora), or on Threads (threads.com/@ithora), and I answer most replies. If you want to talk about something specific rather than in public, the contact page is at ivanturkovic.com/contact. And if you are hiring, or you need a fractional CTO who can tell you where AI helps your engineering team and where it quietly hurts it, book thirty minutes at cal.eu/ivan-turkovic/30min. So what is the last technical book you actually finished, and did it change how you write code or just how you talk about it? --- # AI Won’t Turn Non-Builders Into Builders URL: https://www.ivanturkovic.com/2026/07/15/ai-wont-turn-non-builders-into-builders/ Published: 2026-07-15 Topics: AI Someone showed me their AI-built SaaS last week. Looked clean. Demo worked. They asked me if they could quit their job. I asked what happens when a user signs up tomorrow and the password reset email never arrives. Long pause. That pause is the whole story. AI won't turn non-builders into builders. I watched the same movie with no-code. People loved the idea of building apps without writing code. Very few loved the actual work of dragging boxes around a canvas for nine hours straight. AI is running the same playbook. It makes the first hour feel easy. So more people start. Then the work gets messy, and most of them stop. Building gets messy fast. First you ship the demo. Then comes auth. Then password resets. Then the edge case where someone signs up with a Gmail address that has a plus sign in it. Then bad data from your one paying user. Then a browser bug that only shows up in Safari on iPad in landscape. Then retries. Then logs you can't read. Then a support ticket from someone whose data went missing at 2am. Then five stakeholders showing up with "one small change." That's when the non-builder clocks out. This is not new. Every decade since the 1960s has promised to eliminate the programmer. COBOL was the original. Business managers were going to write their own software in plain English. They didn't. Then came 4GLs. Then visual programming. Then outsourcing. Then no-code. Then low-code. Then AI. Each wave lowered the cost of starting. None of them lowered the cost of owning. That's the part nobody wants to talk about at the events and on the podcasts. "Everyone can build now." Maybe. Everyone can start easier now. Those are different sentences. A demo is not a product. A product is the demo plus three years of edge cases. AI helps you ship the demo in an afternoon. The three years are still three years. I've watched this misunderstanding kill more first-time founders than bad ideas have. They confuse the dopamine of the first commit with the work of the next thousand commits. They confuse "I built a thing" with "I run a thing." Different verbs. Wildly different jobs. Here's the test, if you want one. Imagine your AI-built app starts losing one user a day to a bug you can't reproduce. The logs are useless. The user can't tell you what they did. Your AI tool gives you four different theories and none of them match the data. Do you find this annoying or interesting? If annoying, you'll quit in three weeks. The thing you built will rot. You'll tell people "the tools weren't there yet." That will be a lie. The tools were there. You weren't. If interesting, you'll spend the weekend on it. You'll fix the bug. You'll write a small note about what you learned. You'll go looking for the next one. The people who stay in the loop of build, break, debug, learn, repeat are a specific kind of person. They were a specific kind of person before AI. They'll be a specific kind of person after AI. We call those people software engineers. The tools changed. The job didn't. Final Words If you agree with this, tell me. If you disagree, tell me harder. The conversation is worth more than the post. You can find me on LinkedIn at linkedin.com/in/ivanturkovic, on X at x.com/ithora, and on Threads at threads.com/@ithora. If you want to reach me directly, the contact page is at ivanturkovic.com/contact. If you're hiring a fractional CTO or want to talk through what AI is actually doing to your engineering org, you can book a 30-minute call at cal.eu/ivan-turkovic/30min. So here's my question: when your AI-built side project hits its first ugly bug, are you the person who debugs it at midnight or the person who closes the tab? --- # The Gatekeeper Generation URL: https://www.ivanturkovic.com/2026/07/07/ai-native-developers-gatekeeper-generation/ Published: 2026-07-07 Topics: AI-Driven Development A developer at a fintech client lost three days to a reconciliation job that was silently dropping records under load. He had shipped eleven features in four months. Real output, not demo output. But this bug lived below the framework, somewhere between the ORM and the connection pool, and he could not see down there. He pasted stack traces into the assistant. The assistant proposed six fixes. Five made things worse. On day three a colleague who has been writing backend systems since 2004 read the logs top to bottom and found it in forty minutes. Not because he is smarter. Because he got burned by this exact class of bug in 2011 and the scar never fully healed. That afternoon has been stuck in my head since. Will developers who started their careers with AI ever build deep understanding of the systems they ship? And if most never do, what happens to the people who have it? Understanding was never taught. It was inflicted. Nobody I respect learned systems from a book. The machine forced it on you. The migration that locked a table for forty minutes taught you indexes. The 2am outage taught you TCP timeouts. The memory leak you chased for a week taught you more about garbage collection than any course could. The pain was the curriculum. There was no opt-out. Charity Majors calls software an apprenticeship industry and she is right. You cannot read your way to engineering judgment. You build it by doing the work badly next to people who do it well. Here is what changed. AI removed the pain. Not the bugs. The pain. The productive struggle that used to be mandatory is now optional. Error appears, you paste it. Fix appears, you apply it. The loop closes in ninety seconds and nothing sticks. Depth used to be inflicted on everyone. Now it is an elective. Electives have terrible enrollment. The research already answered the question I wanted this to stay an open question. The data closed it. Anthropic published a randomized controlled trial in January. 52 engineers learning an unfamiliar Python library. The group working with AI assistance scored 17% lower on a comprehension quiz than the group coding by hand. Almost two letter grades. Debugging skills dropped hardest. And the AI group finished about two minutes faster, a difference that was not even statistically significant. They traded understanding for nothing. One detail in that study matters more than the headline. Participants who fully delegated to the AI finished fastest and scored 39%. Participants who used the AI to ask conceptual questions scored 65% and above. Same tool. Opposite outcomes. How you use it decides whether you learn. MIT wired 54 people to EEG caps and had them write essays over four months. The LLM group showed the weakest brain connectivity of any group and could not quote from their own essays minutes after finishing them. The researchers named it cognitive debt. Accurate name. It compounds like the financial kind. Microsoft and Carnegie Mellon surveyed 319 knowledge workers and found the mechanism: the more people trust the AI, the less critically they examine its output. Confidence in the tool replaces confidence in yourself. So the better the models get, the less the average user verifies. Exactly when verification matters most. Namanyay Goel wrote a post last year that traveled the whole industry because every engineering manager recognized it. Juniors with assistants running around the clock, shipping faster than ever, unable to explain what they shipped. I made a version of the same argument when I wrote that AI made learning feel pointless. The research above just put numbers on it. These are not studies about lazy people. They are studies about incentives. The tool rewards delegation. Delegation blocks learning. That is the trap, and it is structural. But we never understood the layer below either I can hear the counterargument because I have made it myself. Nobody writes assembly. Nobody reads compiler output. Every generation stands on abstractions the previous generation swore you had to master. I never punched a card. I turned out fine. Correct. And wrong. Compilers are deterministic. GCC does not hallucinate. A compiler from 2003 produced the same output for the same input, every single run, and when it did not, that was a compiler bug and it made the news. The abstraction contract held, so ignorance below the line was safe. You could stand on the floor without inspecting it every morning. LLMs break that contract. Same prompt, different code. Plausible, confident, and wrong in ways statistically shaped to look right. An abstraction you must verify is not an abstraction. It is a coworker. A fast, tireless, overconfident coworker. You cannot verify what you do not understand. That is the specific trap for AI-native developers. They need verification skill more than any generation in the history of this profession. They get the least practice building it. I wrote before that writing code got easier and being an engineer got harder. This is the same tax, applied to people who never got to build the muscle that pays it. The gatekeepers are already forming Look at who actually ships AI code to production. Fastly surveyed 791 developers last summer. 32% of seniors with ten or more years of experience said over half their shipped code is AI generated. For juniors it was 13%. Seniors ship two and a half times more AI code, and the reason is blunt: they can look at output and know when it looks right but is not. The people with the deepest manual experience extract the most value from the tool that was supposed to make their experience obsolete. Anthropic's earlier research found AI speeds you up by around 80% on tasks where you already hold the skill. Their new study shows the same assistance blocks you from acquiring skills you do not hold. Read those together. AI compounds existing understanding and taxes missing understanding. That is a wealth transfer. From the shallow to the deep. The gate is forming, and here is the part people get wrong: the gate is not age. The gate is whether you ever opened the hood. It correlates with age only because my generation had no choice. There are AI-native developers right now choosing the hard path, asking the model why instead of just give me, reading code they did not write. They will walk through the gate too. There will not be many of them. I made a prediction earlier this year and I am sharpening it now: the highest-leverage engineering role of the next decade is the Verification Engineer (Turkovic prediction, 2026). The person who signs off. The person regulators, insurers, and boards will eventually require by name. Every industry that automated grew a role like this. Aviation kept type-rated captains after autopilot. Construction kept the structural engineer who stamps the drawings. Software is about to grow its stamping class. Gatekeeping sounds ugly. It is just what verification looks like when verification is scarce. We already ran this experiment. It is called COBOL. You do not need to speculate about what happens when the people who understand a system retire. Around 220 billion lines of COBOL still run in production. An estimated 3 trillion dollars in transactions pass through it daily. 95% of ATM transactions touch it. When New Jersey's unemployment system buckled under pandemic load in 2020, the governor publicly asked for volunteer COBOL programmers. Retired ones. There is a company literally named COBOL Cowboys that contracts retirees back into banks, some of them earning more now than they did in their full careers. Here is the detail everyone misses about COBOL. The problem was never syntax. A competent developer learns COBOL grammar in two weeks. The problem is that nobody left remembers why the batch job must run in that exact order on the last business day of the month. What died was not the language. It was the context. The decisions. The scar tissue. Now scale it. COBOL is one language in one sector and it produced a decades-long crisis. I documented every wave of technology that was supposed to eliminate programmers, and each wave left behind systems that outlived their builders. The AI-era version is every language in every sector, plus a generation trained by its own tools not to look underneath. When the old ones retire Do the math with me. The last cohort forged the old way entered the industry around 2022. They retire in the 2060s. But the engineers with twenty years of scar tissue, the ones you actually call at 2am, are mostly 45 to 60 right now. That group is gone by the early 2040s. Three futures. I think we get all three at once. AI closes the gap itself. The models become the deep understander, reading systems better than any human. Partially true already. But then a new question arrives. Who verifies the verifier? The trust problem does not disappear. It moves up one level and gets more expensive. Understanding becomes a priesthood. Systems archaeologists billed like forensic accountants, flown in when the ledger stops balancing and the AI keeps confidently proposing fixes that make it worse. COBOL Cowboys for everything. I would put real money on this one. It is already the fractional CTO business model, just earlier on the curve. Depth becomes a classical discipline. Taught like Latin. Studied by few, dismissed as impractical, and desperately needed at the worst possible moments. The part that actually worries me is none of those. It is this. The models trained on everything we wrote down. Every public repo, every Stack Overflow answer, every postmortem that got published. But the reason the retry logic has jitter, the outage that explains one weird timeout value, the migration that failed in 2014 and shaped every schema decision since. Nobody wrote that down. It lived in people. The models have our syntax. They do not have our scars. The short version Developers who start with AI can still build deep understanding. The research shows exactly how: use the model to ask why, not just to produce. Almost none will, because nothing forces them to, and pain was always the teacher. The few who choose depth, plus the pre-AI generation, become the verification layer for all software. That layer ages. Then it retires. Somewhere in the 2040s we find out what a civilization pays when nobody remembers why the systems work. We already know the small-scale answer. It costs whatever the retired COBOL programmer decides to charge. Final Words. I might be wrong about the timeline. I might even be wrong about the gate. If you believe AI-native developers will build the same depth through different means, make that argument to me, because nobody has made it convincingly yet. And if you have watched this play out on your own team, in either direction, I want to hear what you saw. You can follow my longer thinking on LinkedIn, argue with me on X, or catch the shorter takes on Threads. If you want to talk through any of this in the context of your own product or engineering organization, reach out through my contact page. And if you are hiring or need a fractional CTO who can tell you which side of the verification gap your team sits on, book a call: cal.eu/ivan-turkovic/30min. Who on your team can still read a stack trace without pasting it into a chat window? --- # Fable 5 Is Back. The AI Coding Market Already Moved On. URL: https://www.ivanturkovic.com/2026/07/02/state-of-ai-coding-july-2026/ Published: 2026-07-02 Topics: AI, Software Engineering On June 12, the US government told Anthropic to pull Claude Fable 5. Three days after it launched. The order said no foreign nationals could touch it, including Anthropic's own foreign employees, and since nobody could verify nationality on every request, the model went dark for everyone. API, Claude.ai, Bedrock, all of it. Gone. On June 30 the controls came off. On July 1 it was back in your editor. Three weeks offline. National security. A frontier model too dangerous to export. Here's the part that got buried. Amazon researchers found a way to get Fable 5 to flag software vulnerabilities, and in one case show how to exploit one. So Anthropic tested that against a stack of weaker models. Opus 4.8 found the same bugs. So did GPT-5.5. So did Kimi K2.7, a Chinese model you can download for free. Every model they tried could reproduce the exploit demo. Haiku 4.5 could do it. We spent three weeks treating one company's model like enriched uranium. The capability was already sitting on Hugging Face. That is the state of the AI coding market in a single story. Everyone is guarding a frontier that stopped being scarce. The frontier is cheap now Look at where the tokens actually go. By May, Chinese open-weight models were around 61% of everything routed through OpenRouter, the largest neutral model router. Four of the five most-used models were Chinese. Meta's Llama, the open-weight leader eighteen months ago, fell off the list entirely. And more than half of that traffic is code. People aren't running these models to write poems. They're shipping software with them. In one seventeen-day stretch this spring, four Chinese labs dropped open coding models that trade blows with the Western frontier. GLM-5.1. MiniMax M2.7. Kimi K2.6. DeepSeek V4. None of them costs more than a third of the closed US models. DeepSeek runs around thirty cents per million tokens. Claude runs ten dollars. Same task. Thirty-five times the price. A part-time developer in San Diego told Rest of World that an hour of coding cost him ten dollars on Claude and under fifty cents on DeepSeek. The founder of Lindy switched off Anthropic and said it saved his company millions. His line was better than anything I could write: you don't need God to write your email. That is the real competitive pressure. Not a leaked frontier weight. A cheap, good-enough model that does most of the work for a tenth of the price. The export controls were fighting the last war. The money says the tool is the prize Meanwhile the valuations went vertical. Cursor crossed two billion dollars in annual revenue by February and is raising at fifty to sixty billion. SpaceX took an option to buy the whole company outright for sixty billion, or walk. It's the fastest-scaling B2B software company on record. Zero to two billion in under three years. Slack took seven years to reach its first billion. Cursor did it in eighteen months. Cognition, the Devin people, raised at twenty-six billion on under five hundred million in revenue. That's a richer multiple than Cursor, which earns four times more. The market is betting the autonomous agent has a bigger ceiling than the assisted editor. Even the hype turned over. In February, Andrej Karpathy called vibe coding passé. The same guy who coined the phrase a year earlier. Twelve months from meme to obsolete. That's the clock speed we're running at. Read those numbers and you'd think the tool is the prize. It isn't. The tool is becoming a commodity feature. Microsoft bundles Copilot into VS Code at basically no marginal cost. Anthropic ships Claude Code on its own API. Google and Amazon can hand out coding agents as loss leaders to sell more cloud. When every environment ships a competent agent, best editor is a lead you hold for one release cycle. Cursor is winning right now because it has the best product. Best product is the most fragile moat in software. Seventy percent of engineers already run two to four of these tools at once. Nobody is loyal. They route the problem to whatever wins the problem. What actually changed for the people doing the work Strip out the funding rounds and the export drama. What moved on the ground? Adoption is over as a question. Ninety-five percent of engineers in the Pragmatic Engineer's 2026 survey use AI tools every week. Three quarters use them for at least half their work. Nobody is asking whether anymore. They're asking which. But throughput did not jump the way the pitch decks promised. METR ran the cleanest test we have and found experienced developers were 19% slower on real tasks with AI, while they believed they were 20% faster. DORA found the same shape at the system level: individuals feel faster, delivery gets worse. Everyone ships more code. Not everyone ships more working software. Those are different things, and the gap between them is where this year actually lived. The jobs picture split down the middle. Entry-level postings are down somewhere between 30 and 40% from the 2022 peak. Senior and staff roles are growing. A senior engineer with Claude Code does the work of a small team. That same tool in the hands of someone who has never designed a system produces confident, broken code that falls over in production. So the market pays up for the people who can tell the difference, and stops hiring the people who were on their way to learning it. Which is the problem everyone saw coming and walked into anyway. Gartner says half the companies that cut jobs for AI reasons will rehire for the same roles. Forrester says 55% of employers already regret those layoffs. A third of them spent more restaffing than they saved. Klarna went all in on replacing people, then started hiring them back. Salesforce hired zero engineers in fiscal 2026. And the same firms cutting seniors are quietly reposting junior reqs six months later, because the codebase is a mess and nobody junior was around to learn it. We ran this exact experiment after 2008. Freeze junior hiring, and three years later you can't find anyone with three years of experience. AI didn't repeal that. It handed everyone a better excuse. And the Bureau of Labor Statistics still projects software developer employment growing 15% through 2034. The death of the job got announced again. It keeps not happening. The part that didn't move Here is what a month of very loud headlines did not touch. The model got banned and came back. The frontier got cheap. A code editor became worth sixty billion dollars. And the actual job, the thing you get paid for, is exactly what it was. Someone still has to know what to build. Someone has to read the code the agent wrote and catch the subtle wrong thing before it reaches a customer. Someone has to own the system at 3am when it goes down, understand why it went down, and decide what to do next. AI made writing code cheap. It made none of that cheap. If anything it made it worth more, because there is now ten times as much confident, plausible, untested code flowing toward production, and someone has to stand at the gate. The tools changed again this year. They'll change again next year. The job didn't move. Final Words I could be wrong about the timeline. Maybe the agents get good enough next year that the gate I'm describing shrinks to almost nothing. I don't think so. But I've been doing this long enough to have been wrong before, and I'd rather hear the counter-argument than pretend it doesn't exist. If you think a scarce frontier still matters more than I'm giving it credit for, tell me why. You can find me on LinkedIn at linkedin.com/in/ivanturkovic, on X at x.com/ithora, and on Threads at threads.com/@ithora. That is where most of these arguments actually get tested. If you want to talk any of this through in the context of your own team, my contact page is at ivanturkovic.com/contact. And if you are a founder or CTO trying to work out where AI genuinely helps your engineering org and where it quietly hurts it, you can book a call at cal.eu/ivan-turkovic/30min. So here is my question. If a frontier model can vanish for three weeks and your team barely notices because the cheap open one covered the work, what were you paying the premium for? --- # The New Burnout Comes From Watching, Not Building URL: https://www.ivanturkovic.com/2026/05/27/the-new-burnout-watching-not-building/ Published: 2026-05-27 Topics: AI, Software Engineering A developer with 20 years on the job told me something last week. His old burnout used to make sense. Eight hours of code. Brain fried. Go home. Sleep. Repeat. Predictable. His new burnout is harder to describe. He asks AI to do something. He reads what it produced. Something looks off. He asks again. He notices a regression in a file he never opened. He redirects. The AI confidently takes a wrong turn. He pulls it back. He ships less code than he used to, and feels more drained doing it. It sounds easier than writing the code yourself. It is not. The 200 hour robot run is the perfect metaphor Last week Figure AI livestreamed a humanoid robot sorting packages for 200 hours straight. The unit was called F.03, nicknamed Rose. Zero hardware failures. 249,560 packages processed. Self-charging fleet rotation. Onboard inference via their Helix-02 model. No human teleoperation, no remote control, no reset. By any objective measure, a triumph for the autonomy stack. Watchers were spotting things. One yellow package gave the robot trouble for a long stretch. People noted the robot seemed less aware of its environment than a human would be. Subtle drift. Small failure modes. Things a casual observer would miss but a careful one could not stop noticing. That is the job now. Watching the autonomous system run, and noticing the parts that do not quite work. Not building. Not sleeping either. Why supervision drains differently than building Writing code is one kind of cognitive load. You hold the problem in your head, you decide, you produce output. There is a beginning, a middle, and an end. You finish a function. You breathe. You start the next one. Supervising an autonomous system is a different load. There is no rhythm. The AI does not pause when it is done. It produces, and you check, and it produces, and you check. Your attention can never relax because the model that confidently solved the last six things might confidently break the seventh. The mental shape is closer to managing a junior than to writing code. A junior who never gets tired. Never asks for clarification. Never sleeps. Never learns from yesterday's mistake. Produces three times faster than the senior reviewing them. That last part is the trap. Because they produce faster than you can review, your queue grows. The work compounds in the wrong direction. You finish the day having reviewed twelve PRs and shipped two. You feel behind. You are behind. The metric you used to measure yourself by, "lines of code I wrote today", does not apply anymore. The metric that should replace it, "judgment calls I made well today", does not show up in any dashboard. So you feel less productive, even when the org is shipping more. The Figure AI livestream is what our work feels like now A team of engineers built a system that runs autonomously. They went home. They came back. The system was still running. Champagne came out at hour 200. But the actual watching of the run? That was a livestream. Thousands of people sat there for nine days noticing things. The package that got stuck. The motion that looked off. The failure modes nobody had documented yet. That is our job now. We are not only the engineers who build the system anymore. We are the engineers who watch it. And nobody talks about how tiring that is, because the building part used to be the only thing we measured. What actually helps Batch the AI work. Do not oscillate between writing prose and reviewing AI output every six minutes. The context switch is what kills you. Set aside two hours for AI-assisted work, then turn it off. Write something yourself with no autocomplete. The brain needs a break from supervision. For anything close to the core of the product, write it yourself. The overhead of getting AI to write code you trust often costs more than just writing it. The simple version of this rule: if you would lose sleep over a bug in this file, do not outsource it to a system that does not lose sleep. Treat AI output like junior PRs. Review them with the same care you would a junior submission. Push back. Ask why. Do not merge because the code compiles. Compilation is the lowest bar a piece of code can clear. Build in deliberate offline blocks. Not vacation. Just hours where the tool is off. The autonomy of the system means you have to manufacture your own breaks. They do not happen by themselves anymore. The thesis in fewer words The tools are not the problem. The rhythm is. Eight hours of building used to drain you in a predictable way. Eight hours of watching an autonomous system drains you in a way nobody named yet. It feels lighter in the moment. It is heavier at the end of the day. The Figure AI team got champagne after 200 hours. We get the next ticket. Final Words I would love to hear if this matches what you are feeling, or if your experience is the opposite of mine. The post is half-finished without the responses. You can find me on LinkedIn (linkedin.com/in/ivanturkovic), X (x.com/ithora), and Threads (threads.com/@ithora). If you want to reach me directly, the contact page is at ivanturkovic.com/contact. For hiring or fractional CTO inquiries, you can book a 30-minute call at cal.eu/ivan-turkovic/30min. So tell me: are you shipping more or less code than you were a year ago, and does it feel like you are doing more or less engineering? --- # Is AI Quietly Contracting the World Economy? URL: https://www.ivanturkovic.com/2026/05/10/is-ai-contracting-world-economy/ Published: 2026-05-10 Topics: AI Is AI Quietly Contracting the World Economy? A Quebec translator I read about made roughly six figures in 2023. By late 2025 her work requests had dried up. Total earnings for the year barely cleared €8,000. She is forty-four years old and watching her career end through email, while large language models charge a few cents to do what she charged a few dollars per word to do. Multiply her by a few million. That is the whole argument I want to walk through today. Not the productivity story. The demand-side story. The one nobody at Goldman Sachs models because their clients are long Nvidia. The Mechanism, Stated Plainly Here is the chain. A small business in Berlin used to pay a translator €2,000 to localise a website. Now it pays DeepL €30 a month. The translator used to spend that €2,000 on rent, groceries, a dentist, a plumber, a meal out. Each of those people spent it again. The Keynesian multiplier is not a theory you have to believe in. It is just bookkeeping with a delay. Strip the translator out, and the €2,000 does not vanish. It moves. It moves to a hyperscaler in Virginia, to Nvidia in Santa Clara, to TSMC in Hsinchu. It buys GPUs and electricity and circulating coolant. Some of it gets paid out as dividends to the top wealth decile, who already own everything they need. The marginal propensity to consume of a Frankfurt translator is close to one. The marginal propensity to consume of a Microsoft retained earning is close to zero. That is the entire thesis in one paragraph. How Big Is The Substitution Start with the freelance market because it is the only place we have clean data. A study co-authored by Xinrong Zhu of Imperial College London Business School with Harvard Business School and the German Institute for Economic Research, published in Management Science, tracked 1,725,587 freelance job posts across 61 countries. Within eight months of ChatGPT's launch, writing jobs fell 30.37 percent. Coding fell 20 percent. Graphic design fell 17 percent. The Vollna Upwork report shows writing projects down 32 percent year over year in 2025, the largest drop of any category. Eleven of twelve major work categories declined. Entry-level project share on Upwork collapsed from 15 percent to under 9 percent. A February 2026 Ramp study called "Payrolls to Prompts" found that more than half of the businesses that spent on freelance platforms in 2022 had stopped entirely by 2025. Freelance marketplace spend as a share of total company spend fell from 0.66 percent to 0.14 percent. AI model spend went from zero to 2.85 percent of company budgets. That is not a rotation. That is a substitution. Money that used to pay humans now pays GPUs. The translation industry shows the same shape. CSA Research has the global language services market at $49.7 billion in 2023, down from $52 billion the year before, with per-word rates compressed 50 to 70 percent on commodity work. Machine translation post-editing pays roughly 25 percent of traditional rates for comparable time. The BPO sector employs around 8 million people across India and the Philippines. The IMF estimates one-third of Philippine jobs are highly exposed to AI. The ILO's 2017 ASEAN report found 89 percent of salaried workers in the Philippines BPO sector fall into the high-risk category for automation. Industry analysts now project 1 million BPO and IT outsourcing roles will be impacted by 2030, with 2 to 3 million workers seeing major disruption. TCS cut 12,000 roles in its largest reduction ever. India's top IT firms added a net 17 employees in the first nine months of fiscal 2026. Seventeen. Junior coders who used to be the on-ramp into the global tech middle class are getting fired by email. The Local Economy Loses Twice When a Manila call center agent earning $400 a month gets replaced by an AI agent, two things happen. First, $400 a month leaves the local economy. The sari-sari store, the jeepney driver, the landlord. Gone. Second, the dollars now flow to OpenAI, Anthropic, Microsoft Azure, AWS. Most of that revenue ends up paying for Nvidia chips made in Taiwan and electricity in Virginia. None of it lands in Quezon City. Apply this across translators in Madrid, copywriters in Belgrade, illustrators in Buenos Aires, junior developers in Bengaluru. The capital concentration is not subtle. The IMF says nearly 40 percent of global employment is exposed to AI, rising to 60 percent in advanced economies. Goldman Sachs said in 2023 that AI could substitute for up to a quarter of current work, exposing 300 million jobs in the US and Europe. McKinsey put the addressable annual value at $2.6 to $4.4 trillion across 63 use cases. Read those numbers as someone other than a sell-side analyst. They are mostly a transfer estimate, not a wealth creation estimate. What The Optimists Say I have to do this part honestly because the optimist case is not stupid. Goldman Sachs models a 7 percent boost to global GDP over a decade and a 1.5 percentage point lift to US labor productivity. The Briggs and Kodnani paper from 2023 is still the consensus bull take. McKinsey is in roughly the same neighborhood. The IMF in January 2026 raised its global growth forecast for the year to 3.3 percent, citing AI investment as a key driver. Marcello Estevão at the IMF wrote in March 2026 that AI-related investment now accounts for a large share of US GDP growth. This part is empirically true. Apollo Global Management calculates hyperscaler capex around $646 billion in 2026, roughly 2 percent of US GDP. Bank of America puts AI capex from the top five hyperscalers at $399 billion in 2025, rising past $600 billion. White House AI Czar David Sacks, citing a May 2026 Morgan Stanley report, said AI was 75 percent of US Q1 2026 GDP growth. Pantheon Macroeconomics found that without AI capex, US private fixed investment in 2025 would have been negative. So yes. The buildout is currently keeping the American economy out of recession. The Jevons paradox argument is also real. Cheaper code means more code gets written. Cheaper translation means more content gets localised. AI-specialised freelancers on Upwork command 25 to 60 percent higher rates than general practitioners. Per Upwork's Q4 2024 earnings release, freelancers working on AI-related projects earned 44 percent more per hour than those on non-AI work. CEO Hayden Brown calls these the "AI generalists" and says they are the most sought-after profile in the ecosystem. The historical analogues are well-rehearsed. ATMs were supposed to kill bank tellers. Teller employment grew for two decades after ATMs because cheaper branches meant more branches. The internet was supposed to kill travel agents. It did kill them, but it created software developers, SEO specialists, growth marketers. Disruption with reinstatement. What Is Different This Time Three things, and I am going to be blunt. First, Acemoglu. The Nobel laureate ran the same task-based math and got a 0.66 percent total factor productivity bump over ten years, and a GDP gain between 1.1 and 1.6 percent. His point is not that AI does nothing. His point is that only about 5 percent of tasks can be profitably automated in the near term, and the macroeconomic effect of automating them is small, while the displacement effect on the workers doing those tasks is concentrated and severe. He explicitly notes that "productivity gains from AI are unlikely to lead to sizable wage rises." Other recent work pushes harder. Falk and Tsoukalas's "AI Layoff Trap" model concludes that "at the limit, firms automate their way to boundless productivity and zero demand." Korinek and Stiglitz model a temporary labor demand collapse. Roubini, in a Bloomberg interview this February, invoked Marx directly: workers losing wage share lose the purchasing power to buy what the economy produces. Second, the AI companies are losing money. OpenAI posted a $13.5 billion net loss in the first half of 2025 alone, against $4.3 billion in revenue. Internal projections show 2026 losses of $14 billion against $13 billion of sales, with operating losses of $74 billion in 2028. HSBC analysts estimate OpenAI faces a $207 billion funding shortfall through 2030. Every dollar a translator loses is not currently a profitable dollar in OpenAI's pocket. It is a subsidised dollar, paid for by SoftBank, Microsoft, Nvidia, sovereign wealth funds, and the bond market. The circular financing is not subtle. Nvidia announces a $100 billion investment in OpenAI, OpenAI buys Nvidia chips. Oracle commits $300 billion in cloud to OpenAI, then plans roughly $40 billion in Nvidia GB200 chips to deliver it. CoreWeave gets equity from OpenAI, rents Nvidia GPUs back. Michael Burry is shorting it. Sam Altman has called it a bubble. Ray Dalio compares it to dot-com. If the bubble pops, the displaced translator does not get her job back. The substitution is sticky. The capital is not. Third, the energy bill is hitting voters. The IEA expects global data center electricity consumption to roughly double to 945 terawatt-hours by 2030. US data centers alone accounted for around half of all US electricity demand growth in 2025. PJM capacity prices went from $28.92 per megawatt-day in 2024/25 to $329.17 in 2026/27 to $333.44 in 2027/28, a roughly tenfold jump, with PJM's own market monitor Monitoring Analytics attributing $9.3 billion of the 2025/26 increase, 63 percent of it, to data centers. NRDC estimates $100 to $163 billion in cumulative PJM ratepayer costs through 2033 absent regulatory action. Pepco residential customers in DC saw bills rise around $21 a month from June 2025. AEP Ohio's standard offer jumped 36 percent. BGE customers in Maryland saw $32 added in September 2025. Maine residential rates are up 22.6 percent year over year, though the Maine PUC chair attributes most of that to natural gas and LNG exports rather than data centers directly. This is not just an externality. It is a transfer. Households are subsidising the buildout that is displacing their own labour income. Read that twice. The Geographic Map Globally, I see roughly this shape. The United States captures most of the upside. Hyperscaler capex is keeping GDP positive. Nvidia, Microsoft, Google, Meta, Amazon, Oracle: a handful of companies, roughly 40 percent of S&P 500 market cap, are the recipients of the wealth transfer. The labor market downside is concentrated in entry-level white-collar work. Goldman Sachs' Briggs and Dong concluded in March 2025 that "aggregate labor market impacts are still negligible," which is bull-case framing for "we cannot see the productivity yet either." The European Union takes more of the demand-side hit and less of the capex upside. Stronger labour protections slow the displacement. The ECB has flagged that productivity gains from AI are concentrated in US firms while EU consumers and SMEs absorb the cheaper substitutes. Translators, lectors, content marketers, junior architects, and contract lawyers are all under direct pressure. The EU has neither the data centers nor the model providers in serious scale. Emerging markets get hit hardest on the labour side and gain almost nothing on the capex side. India and the Philippines lose BPO and IT services revenue. Latin American designers and developers, who served the US market through Upwork at lower rates, lose those clients to ChatGPT and Cursor. Africa never had a knowledge economy at scale to lose, but it also will not get one now. Net global picture for 2026: the IMF's 3.3 percent number is plausible only because AI capex is keeping the headline up. Strip out the buildout and the underlying economy is materially weaker. My own back-of-envelope: AI substitution is probably reducing global household income from professional services by $200 to $400 billion in 2026. With a multiplier of around 1.5 in advanced economies, that is $300 to $600 billion of demand drag. AI capex of around $700 billion masks it in the GDP figures. Net visible effect: small positive. Net structural effect: a wealth transfer from millions of professionals to about ten companies. That is not contraction in the headline number. It is contraction in the median household. What Could Change My Mind If the productivity gains show up clearly in measured output for non-AI sectors over the next two years, Acemoglu is wrong and Briggs is right. So far, three years in, they have not. If AI inference costs collapse far enough that the model providers turn cash-flow positive without further capital infusions, the bubble fear goes away. Anthropic looks plausible on this front. OpenAI does not. If displaced workers reskill into AI-augmented roles at scale and recover wages, the demand drag dissipates. The Upwork data suggests this is happening at the top decile of freelancers. It is not happening at the median. Bottom Line AI is not contracting headline GDP in 2026. It is contracting the part of GDP that flows through millions of household budgets, and replacing it with a part that flows through ten balance sheets and a power grid. The capex boom hides this. The bubble math hides this. The 7 percent Goldman number hides this. The Acemoglu number does not. The PJM auction does not. The TCS layoffs do not. The Quebec translator does not. If the buildout pays off, we get a productivity boom and a redistribution problem. If it does not, we get the redistribution problem without the productivity boom. Either way the translator is not getting her clients back. The local economy was always the answer. We just stopped noticing. Final Words I would love to hear where you think I'm wrong on this. The optimists have history on their side: every wave of automation eventually created more jobs than it destroyed. I am not sure this one will, and I am not sure the timing works even if it does. You can find me on LinkedIn at linkedin.com/in/ivanturkovic, on X at x.com/ithora, and on Threads at threads.com/@ithora. If you want to send a longer reply or push back in detail, the contact form at ivanturkovic.com/contact is the best place. If you are building something at the intersection of AI and fintech and want to talk strategy or architecture, you can book a call at cal.eu/ivan-turkovic/30min. So tell me: in your local economy, who has lost income to AI in the last twelve months, and where did that money actually go? --- # Rails + RubyLLM vs LangChain in 2026: The Honest CTO Comparison URL: https://www.ivanturkovic.com/2026/05/03/rails-rubyllm-vs-langchain-2026/ Published: 2026-05-03 Topics: AI-Driven Development, Software Engineering The Email That Started This Post A friend who is a founder sent me a Telegram message last month. "We're three engineers. We have an AI feature to ship by Q3. Everyone says Python. Should I just give up on Rails?" He had a working Rails 8 app. Auth done. Billing done. Three years of customer data. Real revenue. And he was about to throw it out because a YouTube influencer told him "AI means Python." I told him what I'm telling you. Don't. In 2026, if you are building an AI-powered SaaS product, Rails with RubyLLM is the better default for most teams. Not because Python is bad. Because you are not building a research lab. You are building a product. And the gap between "I have an LLM idea" and "I have paying customers" is mostly plumbing. Rails ships the plumbing. LangChain ships an orchestration layer and leaves the plumbing as your problem. That's the whole post. The rest is evidence. What LangChain Actually Is in 2026 LangChain is an orchestration layer for calling LLMs. As of April 2026 the main langchain Python package sits at version 1.0.4, with the legacy abstractions split off into langchain-classic. The repo has roughly 135,000 stars. PyPI shows around 55 million weekly downloads across the package family, which is real. Mindshare won. PyPI Stats LangGraph is now the centerpiece. LangChain Inc. positions itself as "the agent engineering platform" and tells you to use LangGraph for any non-trivial workflow, with LangSmith for tracing and evals and a hosted "LangGraph Platform" for deployment. LangGraph is at version 1.1.x with 31,000+ stars. PyPIGitHub This is three products. Plus a vendor-hosted runtime. Plus a paid observability tool. Plus the actual Python web stack you still have to bring yourself. Harrison Chase wrote in late 2025 that LangChain "grew too fast" early on, that the original library was "too opinionated," and that they later "rewrote the original langchain in 2025 to be more streamlined." Read that sentence twice. The framework you bet on in 2023 got rewritten under you, twice. v0.1, v0.2, v0.3, the deprecation of LLMChain, the migration to LCEL, the reposition around LangGraph, the introduction of langchain-classic. That is not a stable foundation. That is a product company iterating in public. LangChainMedium Practitioners noticed. Octomind's "Why we no longer use LangChain" post is the canonical one: 12 months of production use, then ripped out because "high-level abstractions soon made our code more difficult to understand and frustrating to maintain." Max Woolf's "The Problem with LangChain" post made the same case earlier: simple things become complex, debugging means reading framework internals, the Agent abstraction breaks any time you touch the system prompt. Hamel Husain, who is genuinely warm toward the LangChain team, still recommends LangSmith decoupled from LangChain itself. The most cited counterargument is "they listen to feedback well," which is a culture compliment, not a technical one. OctomindX LangGraph is genuinely better designed than the original chains. It is also still just an orchestration runtime. It does not give you HTTP, auth, jobs, persistence, a UI, or deployment. You still have to build all of that around it. What Rails 8 Actually Gives You Rails 8 ships with the Solid trifecta as defaults: Solid Queue for background jobs, Solid Cache for caching, Solid Cable for WebSockets. All three run on your Postgres or MySQL. No Redis required. No separate worker fleet required. Solid Queue alone is running 20+ million jobs a day at HEY in production. GitHub + 3 Add the rest of the box: ActiveRecord, ActionCable for streaming, Hotwire and Turbo for live UI without React, Kamal 2 for one-command deploys to any VPS, the new built-in authentication generator, ActiveJob, ActionMailer, ActiveStorage, encrypted credentials, Propshaft for assets, and Thruster as the HTTP/2 proxy. CodeMiner42 DHH calls the alternative "merchants of complexity." His Rails World 2025 keynote was a 60-minute argument that we have collectively forgotten how to ship. Eight-day deploy cycles. Three-hour CI. Forty-seven services to render a CRUD form. He was not subtle. "I'm a CRUD monkey. My career is CRUD monkeying. Reading things from a database, creating records, updating records, occasionally deleting them." Most AI SaaS apps are CRUD plus a model call. That is the entire job. Prateek Codes + 2 The under-discussed point: Ruby is unusually token-efficient for LLMs. users.select(&:active?) packs more semantics per token than the equivalent in almost any other ecosystem. Convention over configuration is a token-compression strategy now. Agents working on Rails code stay coherent across longer contexts than agents working on equivalent Python or TypeScript codebases. I've seen this repeatedly on client work. The Pragmatic EngineerIvan Turkovic RubyLLM, The Quiet Default RubyLLM is a single gem by Carmine Paolino. As of late April 2026 it's at version 1.14.1, around 3,900 stars on GitHub, and over 5 million downloads. Three runtime dependencies: Faraday, Zeitwerk, Marcel. That's it. RubyGemsRubyGems It speaks the same API to OpenAI, Anthropic, Gemini, Bedrock, Azure, OpenRouter, DeepSeek, Ollama, VertexAI, Perplexity, Mistral, xAI, and any OpenAI-compatible endpoint. Tokens normalize across providers. Streaming, tool calling, structured output via RubyLLM::Schema, embeddings, image generation, audio transcription, content moderation, multimodal attachments. Same one interface for all of it. RubyGems + 5 class WeatherAssistant < RubyLLM::Agent model "gpt-5-nano" instructions "Be concise and always use tools for weather." tools Weather end WeatherAssistant.new.ask "What's the weather in Berlin?" That's an agent. With tools. With instructions. With provider-agnostic streaming. Compare it to the LangGraph equivalent that imports create_agent, ChatOpenAI, builds a StateGraph, manages a MessagesState, and asks you to think about the runtime. Same outcome. Different cognitive tax. GitHub The Rails integration is the killer. acts_as_chat and acts_as_message give you persisted conversations as ActiveRecord models. rails generate ruby_llm:chat_ui scaffolds a Tailwind chat UI with Turbo streaming, model selection, and tool-call display in under two minutes. New Rails app to working AI chat in 1:46 in Carmine's own demo. The streaming job is six lines: Carmine Paolino + 2 class ChatResponseJob < ApplicationJob def perform(chat_id, content) chat = Chat.find(chat_id) chat.ask(content) do |chunk| chat.messages.last.broadcast_append_chunk(chunk.content) if chunk.content.present? end end end Solid Queue runs the job. Turbo broadcasts the chunks. ActiveRecord persists the conversation. Pgvector handles embeddings as native ActiveRecord scopes. Done. A founder told Carmine: "Our first pass at the AI Agent used langchain. It was so painful that we built it from scratch in Ruby. Like a cloud had lifted." That quote shows up repeatedly. It tracks with what I see on engagements. Carmine Paolino Other Ruby AI Options, Briefly Andrei Bondarev's langchainrb is the original Ruby LangChain port and still maintained, useful if you want LangChain-style abstractions in Ruby. Sublayer takes a generator and agent DSL approach. Raix focuses on object-oriented prompt patterns and now ships MCP integration. instructor-rb mirrors the Python instructor library for structured output. The official Anthropic and OpenAI Ruby gems are fine for direct API access. RubyLLM won the default slot because it has the lowest cognitive overhead, the cleanest Rails integration, the broadest provider support in one interface, and a single maintainer with strong taste shipping weekly. The ecosystem is now large enough to matter: MCP support, OpenTelemetry instrumentation, evaluation frameworks like ruby_llm-tribunal, and prompt templating gems all hang off it. GitHub + 4 What You Actually Need Around LangChain Here is the honest picture of the Python AI stack for a production SaaS in 2026: LangChain or LangGraph for orchestration. FastAPI for the web layer. SQLAlchemy and Alembic for the database. Pydantic for schemas. Celery and Redis for background jobs. A separate Redis for caching. A separate WebSocket layer (or you bolt on Channels-style infrastructure). gunicorn or uvicorn for serving. Docker plus Kubernetes or a managed PaaS for deploy. A frontend in React or Next.js because Hotwire's Python equivalent doesn't exist. Auth via Auth0 or Clerk because there's no canonical answer. Then LangSmith on top, which is a paid SaaS with usage-based pricing, for tracing. That is ten production dependencies before you've written a single business rule. Each one has its own version churn, its own maintainer drama, its own deploy story. I've shipped this stack. I've also paid the bill on it. After 20 years of running engineering teams in fintech, payments and blockchain, I will tell you plainly: every additional moving part is a 2 AM page waiting to happen. Rails gives you all of that in the framework. ActiveRecord, ActionCable, Solid Queue, Solid Cache, Hotwire, the auth generator, Kamal. One Gemfile. One deploy command. RubyLLM slots in as one more gem. The Real Production Comparison Shopify is the loud datapoint here. Sidekick, the AI assistant embedded in every Shopify admin, is built on Rails. The 2025 Rails World talk by Andrew McNamara and Charlie Lee is titled, literally, "LLM Evaluations & Reinforcement Learning for Shopify Sidekick on Rails." Same company that runs the largest commerce monolith in the world is also running a production agentic system on the same stack. Not a microservice in Python with an API. A Rails service with LLM calls, tool routing, JIT instructions, and an evaluation framework on top. Shopify + 2 GitHub still runs Rails. Basecamp and HEY still run Rails. 37signals serves their entire AI feature surface from Rails. Airbnb's monolith is still Rails. The pattern is unambiguous: companies with real revenue and real scale ship AI features on top of the framework they already trust. They do not rewrite to Python because they need a model call. LangChain has its own production roster. Klarna, Replit, Uber, J.P. Morgan, Elastic, Ally Bank are all named on LangGraph's site. Notice the pattern. These are large enterprises with platform teams who can absorb the operational complexity of running LangGraph plus FastAPI plus the rest. That is not your three-engineer SaaS startup. That is a different shape of problem. When Python Genuinely Wins I am not going to pretend otherwise. If you are training or fine-tuning models, Python is the only adult option. PyTorch, transformers, the entire research ecosystem lives there. If you need numerical work, vector math, or ML-adjacent code, Python wins. If your team is full of ML engineers who think in notebooks, do not force them into Ruby. Carmine Paolino LangGraph itself is a legitimately good piece of engineering for one specific case: long-running, durable, stateful, multi-agent workflows where you genuinely need checkpointing, human-in-the-loop, and time-travel debugging across complex graphs. If that is your product, use it. LangSmith for traces and evals is a strong product even if you abandon the rest of LangChain. Hamel Husain runs his consulting practice on top of it, and he's correct to. Hamel There is also a talent argument. The pool of engineers who know LangChain is larger than the pool who know RubyLLM. If you are hiring fast and don't care about the rest of the stack, that matters. But notice what these cases have in common. They are all about ML depth or framework familiarity, not about shipping a SaaS faster. The minute the question is "how do we get auth, billing, background jobs, a chat UI and an AI feature into production by Friday," Rails wins. Why This Matters More in 2026 Than Ever Two trends compound. First, AI agents are now writing meaningful chunks of production code. I wrote about this in my piece on the middle loop, where engineering moved upstream into spec, review and architecture. The constraint is no longer typing speed. It is human decision capacity. Frameworks that minimize cognitive overhead per feature shipped now dominate. Rails was already optimized for this. Convention over configuration is exactly what an LLM needs to produce correct code on the first pass. The Pragmatic Engineer Second, the AI hype cycle is repeating the same script we ran in the 80s and the 2010s. I covered this in the eternal promise of eliminating programmers. Every cycle promises that complexity is going away. Every cycle, complexity gets relocated. The teams that win are the ones that pick the boring stack and ship. The teams that lose are the ones that build a Kubernetes cluster to host an OpenAI API call. DHH said in his Pragmatic Engineer interview last year that beautiful code is a signal of correctness. He's right, and it now matters more than it ever did, because you and your agents are reviewing code at speed. Rails reads like prose. LangChain reads like Spring. The Pragmatic Engineer My Recommendation If you are a product team of one to fifteen engineers building an AI-powered SaaS in 2026, your default should be: Rails 8 with RubyLLM. Postgres with pgvector. Solid Queue and Solid Cache. Hotwire for the UI. Kamal to a small fleet of VPS instances. LangSmith or OpenTelemetry plus the OpenTelemetry RubyLLM gem if you need traces. That's it. If you are doing fine-tuning, custom inference, model hosting, or research, build that part in Python. Talk to it over an API. Keep your product on Rails. If you are convinced LangGraph's graph runtime is necessary because your workflow has fifty stateful nodes with branching durability, you have a different product. Use it. But verify that claim hard before you commit. Most of the time, "we need a graph framework" turns out to mean "we have one prompt and a tool call." The gap I see most often on consulting calls in 2026 is not that teams picked the wrong AI library. It's that teams picked an AI orchestration framework and forgot to pick a product framework. Rails is the product framework. RubyLLM is the AI orchestration. Together they are the whole package. LangChain is half a stack pretending to be a complete one. If your competitor is shipping features twice as fast as you with a quarter of the engineers, go look at what they're using. It's increasingly Rails. --- # The Middle Loop Is Where Engineering Actually Lives Now URL: https://www.ivanturkovic.com/2026/04/27/the-middle-loop-where-engineering-actually-lives-now/ Published: 2026-04-27 Topics: AI, AI-Driven Development Someone finally named the thing. There's a word now for the work I've been doing for the last two years without a vocabulary for it. The middle loop. Supervisory engineering. The thing that sits between writing code and shipping it. The thing that didn't exist before agents started producing code faster than any human could read it. I've been in this business for over two decades. Twenty years of Rails alone. Built payment systems. Scaled fintech platforms. Watched three hype cycles promise to end programming and then create more programmers. So when I say this particular shift feels different, I'm not talking from a place of panic. I'm talking from a place of "yeah, this one is real, and most orgs are not ready." Let me walk you through what's actually happening, what it means if you're a CTO or an engineer or a founder, and where the industry is quietly breaking. The loops, briefly We've always talked about two loops in software engineering. The inner loop is you and your editor. Write, run, fix, run again. The outer loop is everything that happens once you push: CI, review, staging, deploy, monitor, on-call. Every engineering culture I've ever worked in had these two loops, named or not. There's a third one now. Sitting between them. Directing agents, evaluating their output, catching the plausible-looking-but-wrong results, keeping the pieces architecturally coherent across parallel streams of agent work. Nobody named it for a while. Everyone was doing it. That's usually how these things go. Why this matters more than the usual "AI will change everything" take I've written before about vibing fatigue. The mental tax of operating in an environment where companies measure AI output as if coding was ever the bottleneck. It wasn't. Meetings were. Coordination was. Unclear specs were. The middle loop is the other side of that coin. Agents took over one part of the job. The part that felt like engineering to a lot of developers. Now those developers either move up into supervision or get squeezed out. The constraint shifts from production capacity to decision-making capacity. Read that twice. It's the whole story. If an agent can write a hundred lines of code while you're reading twenty, the bottleneck is you. Not the AI. You. Your capacity to decide what's right. What's shippable. What's quietly broken. What's architecturally coherent with the other four streams of agent work happening in parallel. Most engineers were never trained for this. Most career ladders don't recognize it. Most management structures pretend it doesn't exist. What the middle loop actually demands The people who do this well share three traits. They think in terms of delegation and orchestration, not implementation. They carry strong mental models of system architecture. They can assess output quality without reading every line. That last one breaks most engineering review cultures. "Looks good to me" PRs were already a problem. Now the PRs come in at three times the rate, written by an agent that has no shame about shipping something that almost works. You can't LGTM your way through that. You also can't read every line. Something has to change, and the thing that changes is how you read code at all. You start looking at shape. At boundaries. At what the code is doing to the rest of the system. Not at syntax. Not at style. Structural comprehension at speed. It's a skill. It's teachable. It's also not what most of us spent our first decade learning. Twenty years of Rails helps here in a specific way. When you've seen the same conventions applied across thousands of apps, you stop reading line by line. You read the deviation. The place where the code stops looking like Rails and starts looking like someone's cleverness. That's where agents tend to go wrong. That's where you learn to look first. Where the rigor goes If agents write the code, where does the engineering discipline migrate to? There are five destinations, and I've seen all of them play out on real client engagements. Upstream to the spec. The highest-leverage place to catch errors is before a single line gets generated. User stories are too vague. Teams are rediscovering old techniques like state machines, decision tables, and structured requirement formats. Not because they're fashionable. Because an agent will produce exactly what you specify, at scale, including the parts you got wrong. Into the test suite. TDD turns out to be the strongest form of prompt engineering anyone has found. The mechanism is specific. Without tests first, an agent will happily write a test that confirms whatever broken thing it just generated. With tests first, the agent can't cheat. The test becomes a contract between what you said you wanted and what got built. Rails people already have the tooling for this. Minitest, RSpec, system tests that read like prose a non-technical founder can approve. If you're building with agents and you're not running tests first, you're leaving the biggest lever on the floor. Into type systems and constraints. Make incorrect code unrepresentable. Pick languages and frameworks that carry the guardrails so the agent has fewer ways to be wrong. Rails does this with convention over configuration. Every convention is a decision the agent doesn't have to make, a misunderstanding eliminated before it starts. Sorbet or RBS on top of that, and you've cut the agent's surface area for creative error to a fraction of what it'd be in a more expressive environment. Into risk mapping. This is the reframe I keep coming back to. The old question was "did someone review this code?" The new question is "what's the blast radius if this code is wrong, and is our verification proportional to that risk?" Not every line deserves the same attention. A settings page and a payments settlement service are not the same animal. Review them like they are and you'll burn the team out and still miss the thing that matters. Into continuous comprehension. If code changes faster than humans can read it, the old model of building understanding through code review breaks. You need architecture retrospectives. Ensemble programming. AI-assisted comprehension tools that generate system overviews on demand. Or, bluntly, you pair program all the time. Not just for the tricky bits. The parts that matter most if you're running the org There's a lot of ground here that doesn't get airtime in the usual commentary. A few of the pieces that should. Cognitive debt. Technical debt has a new cousin. The gap between how complex your system is and how well humans actually understand it. Agents widen this gap every day. Your codebase gets bigger. Your collective understanding doesn't keep pace. Eventually you hit a wall where nobody can explain why the monolith works, and nobody can safely change it. I've seen this already in client orgs. It compounds silently. Agent drift. Put the same database agent on two different teams. Give it six months. The one working on the e-commerce backend and the one working on the ERP system will develop different habits and preferences. They started identical. They're not identical anymore. Conway's Law, but accelerated, because agents pick up local norms without any of the human friction that used to slow that divergence. Do you standardize it out or let teams optimize locally? Most orgs won't settle that question for a while. Security is dangerously behind. This one needs to be said loudly. The industry pattern is to fix security later. With agents, later is too late. Give an agent email access and you've just handed it password resets and account takeovers. Full machine access for dev tools means full machine access for whatever the agent decides to do. Platform engineering needs to drive secure defaults. Individual developers making security-conscious config choices is not a strategy. The productivity/experience decoupling. This one is ugly. For decades, developer experience and developer productivity moved together. Happy teams shipped more. With agents, the two are coming apart. Orgs can squeeze more output from environments where developers report higher cognitive load and lower satisfaction. Which means the business case for investing in developer experience gets weaker, unless the definition changes. Reframing it as "agent experience" gets the wallets open faster. Weirdly, the conditions that help agents perform well overlap almost completely with the conditions that help humans perform well. Same investments, better framing for the CFO. Stability is regressing. A decade of DORA research showed that smaller batch sizes correlate with higher stability. Agents make it trivially easy to produce enormous changesets. Teams are drifting back toward waterfall-shaped releases because the agent can keep up and the human review cadence can't. This is an active regression that very few people are talking about. Staff engineers under pressure. Staff engineers use AI less often than juniors. When they do use it, they save more time per use. Their context makes them better supervisors. They're also the ones most likely to be drowning in coordination work that doesn't need them. They should become "friction killers." Fix the things that slow both humans and agents. Most staff engineers I know have been told for years that there's no budget for the improvements they keep suggesting. Now those improvements are existential. The mid-level squeeze. The one I keep warning people about. Juniors are more valuable than ever because AI got them past the net-negative phase faster and they haven't built up habits that resist new tools. Seniors are more valuable because their judgment is the moat. Mid-levels who came up during the hiring boom and never built the fundamentals are in the worst position in the industry. Retraining them is hard. No org has solved it. If you're mid-level, don't wait for your company to figure it out. The decision-fatigue problem, up close I want to come back to this one because it's where the middle loop bites hardest. You can feel it if you're supervising agents for real. PRs stack up. Each one looks plausible. Each one needs a call. Ship. Don't ship. Fix. Revert. Flag. Approve. Approve. Approve. By mid-afternoon your judgment starts to go. By Thursday you're rubber-stamping things you'd have caught on Monday. This is the new organizational bottleneck. Middle managers who used to be coordination points become approval bottlenecks. Agents generate job specs, code fixes, feature implementations faster than any human can say yes. The honest answer nobody wants to give: you need fewer decisions per human, not more humans. That means clearer risk tiers so low-risk things don't hit your desk at all. It means better tests so the test suite says yes or no for you. It means agent permissions tight enough that some categories of decision never reach you. It means cultural permission to say "good enough to ship" instead of chasing a moving target of cleanliness. That last one is the hardest. When one review tool flags an issue, the fix triggers another tool, which triggers another. Clean code is a moving target. Somebody has to be the grown-up and call it. "This ships. We're done." If nobody plays that role, you burn weeks and tokens on cosmetic perfection while the business waits. The role mutation that's actually happening PM and developer roles are converging. Developers are now thinking more about what to build and why. That's PM work. Meanwhile, some PMs are being trained to work in Markdown inside developer tools. Large tech companies are researching whether the PM role needs a new name. They should. What used to be "I write specs and you implement them" is now "we co-own the spec, and the agent implements it." Completely different shape. Needs a new word. The old line between product and engineering was never that clean anyway. AI didn't create this dysfunction. It exposed it. Knowledge fragmentation between disciplines, unclear role boundaries, cultural gaps. All of it was there. All of it got more expensive to ignore. If you're a PM in 2026 and you can't write a precise spec that an agent can execute, you're in trouble. If you're an engineer who refuses to own the "what" and wants to stick to the "how," you're in trouble. The roles are merging whether anyone wants them to or not. What I tell the founders on my calls Most of the founders I advise don't have a team yet. They have an idea, a budget, and a pile of agent output they can't fully evaluate. They are the inner loop and the outer loop at the same time. The middle loop is the gap nobody fills. My job when I take on these clients is not to write better code than Claude or GPT. That's not the value. The value is knowing which code matters. Where the blast radius is. Which corner of the system is quietly rotting and which looks scary but is fine. When to stop fixing and start shipping. This is also why I'm careful with founders who come to me saying they want me to replace their engineering team with agents. That's not a strategy. That's a wish. The middle loop still needs a human. Maybe one day it won't. That day is not this year. What to do on Monday If you're a CTO, start risk-tiering your code. Not perfectly. Just enough that you stop treating all changes as equally important. If you're a senior engineer, learn to read code without reading every line. Get comfortable with structural comprehension at speed. The skill is teachable. It just isn't taught. If you're a mid-level engineer, your survival strategy is either going deeper into systems and architecture or going broader into spec writing and review. Staying a ticket-translator is not a strategy anymore. If you're a junior, you are in a better position than the discourse suggests. Pair with seniors. Use the agents fearlessly. Ask why at every turn. You are the most productive configuration in the org right now and nobody talks about it. If you're a founder building solo with agents, get someone who knows code to look at what got built. Before you raise. Before you launch. Before you scale. The audit is cheaper than the rewrite. The map is being redrawn. The people best positioned to draw it are the ones willing to admit how little they know. Engineering didn't get easier. It got more honest about what engineering always was. A lot of the framing in this piece was sharpened by reading ThoughtWorks' "The Future of Software Engineering: Retreat Findings" (February 2026). Worth a read if you want the full industry-level view. Final Words I'm curious whether this matches what you're seeing. If you agree, push back on something anyway. If you disagree, push back harder. The middle loop is forming right now and the vocabulary is still being built. You can find me on LinkedIn (linkedin.com/in/ivanturkovic), X (x.com/ithora), and Threads (threads.com/@ithora). I read everything, even when I don't reply right away. If you're working through this in your own org and want a second set of eyes, reach out at ivanturkovic.com/contact. I work with founders and CTOs on exactly this kind of transition. If you'd rather just grab 30 minutes and talk it through, book directly at cal.eu/ivan-turkovic/30min. Question for you: who owns the middle loop in your org today, and do they know they own it? --- # Everyone Is an “Engineer” Now, and Nobody Knows What Anyone Does URL: https://www.ivanturkovic.com/2026/04/24/ai-job-titles-2026-naming-chaos/ Published: 2026-04-24 Topics: AI, AI-Driven Development A CTO's field guide to the 2026 AI job title theater, with a companion reference guide covering all 40 roles. I have been a CTO for most of my adult life. I have hired, fired, onboarded, mentored, and occasionally been forced to explain to finance why a "Senior Applied Generative AI Engineer II" and a "Staff LLM Engineer" should sit in the same comp band. What I can tell you, after twenty years in this circus, is that the AI job title situation in 2026 is the worst naming disaster the industry has produced since we decided "DevOps" was a person rather than a practice. Let me give you the tour. In a single week last month I saw open roles for: AI Engineer, Applied AI Engineer, AI Software Engineer, Generative AI Engineer, GenAI Engineer, LLM Engineer, Prompt Engineer, Context Engineer, Agent Engineer, Agentic AI Engineer, RAG Engineer, AI Systems Engineer, AI Platform Engineer, AI Infrastructure Engineer, AI Reliability Engineer, Model Deployment Engineer, LLMOps Engineer, AI Ops Engineer, AIOps Engineer, AI Evaluator, Evals Engineer, AI Red Teamer, AI Alignment Engineer, AI Safety Engineer, Model Behavior Engineer, AI Trainer, Forward-Deployed Engineer, AI Solutions Architect, AI Product Manager, AI Strategist, Chief AI Officer, Head of AI, AI-Native Developer, AI-Augmented Engineer, and my personal favorite, "Builder." Boris Cherny from Anthropic actually predicted on Lenny's Podcast that the title "software engineer" would disappear and be replaced by "builder," because calling it a quarter to noon on Wednesday clearly wasn't stupid enough. Here is the dirty secret. Most of these titles describe the same three jobs. Job 1: Someone who builds product features using an API somebody else trained. That is 80% of the market. Call them AI Engineer, Applied AI Engineer, GenAI Engineer, LLM Engineer, Prompt Engineer, RAG Engineer, or AI Software Engineer. The work is indistinguishable. They write Python or TypeScript, call OpenAI or Anthropic, stuff retrieval in front of it, write evals, and try to get the thing to stop hallucinating in production. Levels.fyi pegs median total comp for an "AI Software Engineer" at $245K and an "AI Engineer" at $159K. Same human. Different line on the org chart. $86K gap. Job 2: Someone who trains or fine-tunes models. Your actual ML Engineer, ML Research Engineer, or Research Scientist. Median around $265K, PhD common, older discipline. Job 3: Someone who keeps the AI plumbing from catching fire. MLOps, LLMOps, AI Platform, AI Infrastructure, AI Reliability. Pick your poison. Median around $161K. Everything else is branding. The 10 titles worth hiring for These are the roles that describe actual, differentiated work. If your job description matches one of these, great. If it doesn't, you are probably restating one of these in more expensive language. TitleMedian TCVerdictWhat they really do AI Engineer$159K to $245KFastest-growing role in USBuilds product features on top of LLM APIs LLM Engineer$210K to $450KReal specialization, real premiumProductionizes LLMs end-to-end, including fine-tuning Machine Learning Engineer$265KThe stable anchor roleActually trains models from data Research Engineer (frontier labs)$350K to $1.4M+Dominant title inside AI labsResearch plus engineering, one role at OpenAI, Anthropic, DeepMind AI Infrastructure Engineer$220K to $600K+Hot, compute is the bottleneckRuns the GPU cluster and serving stack MLOps / LLMOps Engineer$161K to $240KStable, underpaid for what it isKeeps models and prompts from breaking in production AI Product Manager$180K to $260K+Every product org needs oneOwns the cost/quality/latency triangle Forward-Deployed Engineer$238K to $700KHot and potentially oversoldEnterprise customer-embedded, writes production code Evals Engineer$160K to $400K+The most under-valued title on this listDesigns how you actually know if the AI is good Chief AI Officer$352K to $1M+New but real C-suite seatOwns AI strategy, risk, and governance If you hire cleanly across those ten, you cover roughly 95% of the legitimate work happening in AI right now. Everything else in the reference guide is either a variant of these, a niche specialization, or marketing. The 10 titles that are mostly theater These are the ones I would push back on if they showed up in a req template. Some are real work under a dressed-up label. Some are skills masquerading as jobs. Some are vendor sales moves wearing an engineering uniform. TitleWhy it's theaterWhat it actually is "Builder"Cherny's prediction for end of 2026, not a real titleMarketing for "engineer who uses AI" Vibe CoderCollins Word of the Year. Meme.Someone who accepts all AI diffs without reading Vibe EngineerWillison himself called the name "stupid"A workflow, not a title AI-Native / AI-Augmented DeveloperNot a title, a skillWill be "Software Engineer" in 18 months Prompt EngineerPeaked 2023, now absorbed into AI EngineerNarrow slice of what context engineers do GenAI EngineerBranding variant of AI EngineerConsulting firms preferred label RAG EngineerToo narrow to surviveSkill inside AI Engineer Context Engineer (at consulting shops)Cognizant hired 1,000 with a vendor that sells "ContextFabric"Senior Consultant with a new title AI StrategistSquishy advisory labelConsulting rebrand AIOps Engineer (legacy sense)Naming collision with agent opsIT ops with ML features Context Engineer deserves a note. The underlying discipline is legitimate. Karpathy endorsed the framing in 2025, and the work (retrieval, memory, tool design, governance of what an agent sees) is real. But Cognizant's August 29, 2025 announcement of one thousand Context Engineer hires, in partnership with Workfabric AI which happens to sell a product called ContextFabric, is a marketing stunt wearing an engineering hat. In-house teams at AI labs do context engineering. They just don't hire for the title. "Prompt Engineer" had the same arc in reverse. In 2023 it was supposedly the future of work. Two years later, Glassdoor has the median at $129K, ZipRecruiter has it at $63K, and most serious roles have been quietly rebadged to "AI Engineer" because nobody wanted to be the one who told investors they were hiring English majors to whisper at ChatGPT. Then there is the Forward-Deployed Engineer, which isn't theater but deserves a warning label. Palantir invented the role over a decade ago. In 2025 every AI lab decided they needed them too. FT reported FDE postings jumped over 800% between January and September 2025. Gergely Orosz then pointed out the funny part: developers do not actually want the job. High pressure, customer-facing, seen as less prestigious than "real" engineering, and devs who take it often quit within weeks when they figure out it is consulting with a nicer title. Palantir pays an average $238K TC. OpenAI and Anthropic pushed the bands to $350K to $550K. For that money you get to fly to Fortune 500 HQs and babysit their RAG pilots. Some job. And "vibe coder." Andrej Karpathy coined it on February 2, 2025. Eight months later Simon Willison countered with "vibe engineering" to rescue the term. Willison himself wrote that the name is "stupid" but he wanted the gatekeeping. Fine. But now we have a generation of LinkedIn headlines that read "AI-Native Vibe Engineer | Builder | Forward-Deployed Agentic Context Architect" and my eyes physically hurt. The cost of the theater The naming mess has a direct price. When a company calls the same job three different things across three req IDs, recruiters over-pay the shiniest one by 20 to 40 percent. I have seen a fintech pay $260K for an "LLM Engineer" while paying $180K for an "AI Engineer" doing the exact same RAG work two floors away, because the first req was approved by a VP who read one Substack about LangChain. The title inflation also signals to the market. Once "AI Engineer" becomes the generic label, every niche reinvents itself as a different kind of engineer to escape the pay gravity. This is how you get "Principal Agentic Context Engineer" as a real LinkedIn title. It pays better than AI Engineer because HR has no benchmark for it. What is actually going to happen Most of these titles will collapse into "AI Engineer" over the next 18 months. The prestige labels will survive only at frontier labs where the work is genuinely different. What will emerge in their place are two roles nobody is talking about yet, and they matter more than anything on the current list. The first is the AI Delivery Engineer Not an orchestrator, not a conductor. Someone who owns the full arc from vague business intent to shipped outcome, using AI as the primary production tool. They refine the story description. They decide what technology to reach for. They guide the AI through generation with different levels of automation depending on the risk of the task. They review output, iterate, and ship. This is not a project manager. This is not a prompt engineer. This is the person who turns "we need a customer-facing quote calculator that handles these edge cases" into a working thing, with the AI doing most of the typing and the human doing all of the thinking. In most small and mid-sized companies, this role absorbs what today is split across engineer, PM, and tech lead. By 2027 I expect the AI Delivery Engineer to be the most common engineering title in companies under 200 people. The second is the Verification Engineer This role does two jobs at once. They are a QA tester in the traditional sense, running the system end-to-end and hunting for broken edge cases. But they are also the keeper of a domain knowledge base that lets them verify the AI actually built the right thing for the business, not just something that runs. They check the business logic. They confirm the flow handles the regulatory case, the currency conversion, the off-hours billing exception, the thing that matters to this specific company and no other. This role only makes sense in larger organizations and in anything where a wrong answer has real consequences. Fintech. Healthcare. Industrial control. Legal. Payments. For low-risk code, the AI will self-check or auto-accept. For anything that touches money, safety, or compliance, a human Verification Engineer signs off. The knowledge base they maintain is the product. The checking is the salary. Neither of these roles is a rebrand. Both replace real work that today is scattered across tickets nobody owns. Practical advice If you are a hiring manager reading this, do two things. First, standardize on two engineering titles for the current wave (ML Engineer and AI Engineer) and put specialization in the JD, not the title. Second, start mapping which of your existing roles are quietly becoming AI Delivery Engineer or Verification Engineer roles. The people already doing that work are the ones you cannot afford to lose. If you are a candidate, stop collecting titles like Pokémon. Nobody senior is impressed. The best engineer I hired last year had "Software Engineer" on her LinkedIn and shipped three production LLM systems. The worst candidate I rejected was a "Principal Agentic GenAI Forward-Deployed Context Architect" whose GitHub was three forks of a LangChain tutorial. The models are smart. The titles are stupid. The next generation of titles will not be the ones on the LinkedIn trending list right now. They will be the ones that name work that did not exist before AI made it possible. For the full breakdown of all 40 titles, comp ranges, differentiators, growth trends, and my recommended HR standardization framework, see the companion AI Job Title Reference Guide 2026. Final Words I wrote this partly as a reference for HR teams drowning in req templates, and partly because I am tired of seeing the same three jobs dressed up in thirty-five costumes. Agree? Disagree? Think I got the predictions wrong and you have a better name than AI Delivery Engineer or Verification Engineer? Tell me. I post regularly on LinkedIn (linkedin.com/in/ivanturkovic), X (x.com/ithora), and Threads (threads.com/@ithora). If you want to talk about how to actually structure your AI engineering org, or you're thinking about fractional CTO work, reach out at ivanturkovic.com/contact or book a call directly at cal.eu/ivan-turkovic/30min. What title is your company using for the job that used to be "software engineer," and what do they actually do all day? I genuinely want to know. --- # The Engineering Age That’s Ending, and the One We Haven’t Named Yet URL: https://www.ivanturkovic.com/2026/04/18/engineering-age-ending-ai-software-future/ Published: 2026-04-18 Topics: AI The best engineers I know write less code than they did two years ago. They ship more. Everyone wants the clean story. AI replaces developers. AI makes developers 10x. Juniors are cooked. Juniors are saved. Pick a side. The reality is messier. Big Tech new grad hires dropped to 7% of all new hires, down from 25% in 2023. Computer engineering graduates now have the second-highest unemployment rate of any US major. Higher than philosophy. Only physics is worse. Then Klarna replaced 700 agents with AI in 2024 and reversed in 2025. Duolingo went "AI-first" in April 2025 and walked it all the way back in April 2026. Netflix hired a junior engineer last year for the first time in 25 years. OpenAI and Anthropic are hiring juniors for the first time ever. Both things are true. The old junior role is dying. A new one is forming. The next three years The bottom of the pyramid does not come back. Writing syntax, basic CRUD, standard integrations, these are prompts now, not jobs. But the middle gets squeezed hardest. The mid-level engineer whose main value was translating a ticket into working code has the weakest position in the industry. That translation layer is what the models eat first. Companies that killed their apprenticeship pipeline will spend these three years paying the bill. Code review debt. Architectural drift. Nobody under 35 who understands why the system looks the way it looks. The next five years By 2030 the word "programmer" will feel as current as "webmaster." The spec becomes the code. GitHub shipped Spec Kit. Amazon shipped Kiro. OpenAI's Sean Grove argued code is 10 to 20% of the value an engineer brings. The other 80 to 90% is writing specs that fully capture intent. He's not wrong. He's a year early. By 2030 the primary artifact of engineering work is the specification. The PRD. The detailed story. Executable English. The human writes and owns the spec. The AI writes and maintains the code. The human reviews, steers, and refuses. Amazon's working-backwards culture turns out to have been an accidental prophecy. The next ten years Here is the part nobody wants to hear. Software developer employment in 2036 will be higher than it is today. Not lower. Every "developer-killing" technology in history created more developers. 4GL, Visual Basic, outsourcing, low-code. ATMs were supposed to destroy bank tellers. Teller employment grew for thirty years before mobile banking eventually killed it. Jevons paradox applies to software. When the cost of producing software collapses, demand explodes. Internal tools that were never worth building get built. Bespoke apps for teams of six get built. The demand has no ceiling I can see. But the jobs won't look like today's jobs. A typical org in 2036 has a thin layer of humans directing a thick layer of agents. Taste, judgment, review, direction for the humans. Production for the agents. The age of the programmer is ending. The age of the builder is starting. Most people reading this will be fine. Final Words I could be wrong about all of this. Ten-year predictions usually are. If you disagree, I want to hear it. You can reach me on LinkedIn at linkedin.com/in/ivanturkovic, on X at x.com/ithora, and on Threads at threads.com/@ithora. I read everything. If you want to talk about what this means for your team or your career, the contact form is at ivanturkovic.com/contact. If the spec is the primary artifact of engineering work in 2030, what are you doing today to get better at writing specs? --- # Open Source Security Is Everyone’s Problem Now URL: https://www.ivanturkovic.com/2026/04/14/open-source-security-axios-attack/ Published: 2026-04-14 Topics: JavaScript & Frontend, Ruby on Rails, Software Engineering Two weeks ago, a North Korean state actor compromised the lead maintainer of Axios and published malicious versions to npm. The library has roughly 100 million weekly downloads. The poisoned packages were live for about three hours before anyone noticed. Three hours. That is all it took to potentially compromise tens of thousands of development environments worldwide. The attacker did not find a bug in the code. They targeted the person. Social engineering, a compromised machine, stolen npm credentials. The malicious versions looked identical to legitimate releases. No code review would have caught it because the code never went through the project's CI/CD pipeline at all. This is the reality of open source security in 2026. The most popular packages in the world are maintained by small teams. Sometimes a single person. And that single person is now a target for nation-state attackers. We need to talk about what we can actually do about this. Independent release validators. Open source projects should introduce a validation layer. A small group of trusted reviewers, separate from the core contributors, whose only job is to confirm that a release contains no external risk before it goes live. Versions could ship with a "validated" flag so consumers can decide whether to accept unvalidated releases. For the most critical packages, those first few hours after a new version drops are exactly when the risk is highest. A validation gate would buy time. Hardware-based signing keys. Contributors on high-impact projects should self-enforce the use of hardware security keys for authentication and package signing. If the Axios maintainer's credentials had been tied to a physical device, the attacker could not have published from a different machine. This is not theoretical. FIDO2 keys exist. They work. The friction is minimal compared to the blast radius of a compromised account. Corporate funding for open source security. This one is already starting to happen. The Linux Foundation recently announced $12.5 million in grants from Anthropic, AWS, Google, GitHub, Microsoft, and OpenAI, all directed at strengthening open source security through OpenSSF and Alpha-Omega. That is a start. But $12.5 million spread across an ecosystem with millions of packages is not going to be enough. The companies that profit most from open source need to treat its security as critical infrastructure, not charity. Lock your dependencies. Stop using install commands that resolve versions. Most developers still run npm install out of habit. The problem is that npm install resolves version ranges and can pull newer subversions that match your package.json constraints. That is exactly how the Axios attack spread. If your package.json said "axios": "^1.14.0", a regular install would happily grab 1.14.1, the poisoned version. npm ci does none of that. It reads your package-lock.json and installs exactly those versions. No resolution, no surprises. You can block npm install entirely with a preinstall script: { "scripts": { "preinstall": "if [ \"$npm_command\" = \"install\" ] && [ \"$npm_config_ignore_scripts\" != \"true\" ]; then echo \"Error: Use npm ci instead of npm install\" && exit 1; fi" } } Add --ignore-scripts to prevent postinstall hooks from executing. That is the exact mechanism the Axios attacker used to deploy the RAT: # GitHub Actions - name: Install dependencies run: npm ci --ignore-scripts This is not just a JavaScript problem. Every ecosystem has the same gap. Ruby (Bundler). Never run bundle install without a committed Gemfile.lock. In CI, always use bundle install --frozen. This flag makes Bundler refuse to update the lockfile. If anything drifts from what is committed, the build fails: # GitHub Actions - name: Install gems run: bundle install --frozen --jobs 4 You can also add this to your .bundle/config so no one on the team accidentally resolves new versions locally: BUNDLE_FROZEN: "true" BUNDLE_DEPLOYMENT: "true" Python (pip). Stop installing from requirements.txt with loose version specifiers. Pin every dependency to an exact version and hash. pip install --require-hashes will refuse to install anything that does not match a known hash, which means even if a package is compromised on PyPI, the install fails if the content changed: # requirements.txt with hashes axios-like-lib==2.1.0 \ --hash=sha256:abc123... In CI: # GitHub Actions - name: Install Python dependencies run: pip install --require-hashes -r requirements.txt For a more robust approach, use pip-compile from pip-tools to generate fully pinned and hashed requirements from a loose requirements.in file. This gives you the convenience of declaring top-level dependencies while locking the entire transitive tree. None of these ideas are radical. Validators exist in other trust models. Hardware keys are standard in enterprise environments. Corporate security funding is table stakes for any supply chain you depend on. Lockfile enforcement is a one-line config change. The radical part is that we still do not do any of this at scale. Open source built the modern internet. The least we can do is stop pretending a single maintainer with a password is an acceptable security model for software that runs on every continent. Final Words If you agree with this, share it. If you disagree, I want to hear why. You can find me on LinkedIn, X, and Threads. If you want to talk about open source security, software architecture, or anything else covered on this blog, reach out through my contact page. What would it take to make you trust a new package version the day it drops? --- # Vibing Fatigue: Why Tracking AI Usage Is the Wrong KPI URL: https://www.ivanturkovic.com/2026/04/11/vibing-fatigue/ Published: 2026-04-11 Topics: AI, Software Engineering A developer I know got pulled into a "productivity review" last month. Not because their output dropped. Because their AI tool usage was below the team average. Their manager wanted to know why they weren't using AI for coding enough. Not why their code had fewer bugs. Not why their PRs moved through review faster. Why the dashboard said they weren't vibing with AI hard enough. This is where we are now. They even have a leaderboard in the company and the leader spent $52k in tokens last month. The metric that measures nothing Large companies love measurable adoption. They spent millions on AI tooling licenses. Somebody has to justify that spend. So they started tracking prompts per day, acceptance rates, lines generated. Some even built internal dashboards that rank teams by AI usage. The assumption is simple: more AI usage equals more productivity. If a developer writes 60% of their code with AI assistance, they must be 60% more productive. Right? Wrong. Completely wrong. Writing code was never the bottleneck. Not in any company I've worked with over the past twenty years. The bottleneck was always everything around the code. Meetings. Standups. Sprint planning. Backlog grooming. Architecture reviews. Stakeholder calls. Status reports. Slack threads that could have been a sentence. Follow-up meetings about the meeting you just had. In most large organizations, a developer is lucky to get three to four hours of actual coding time per day. Some get less. The rest is process. Necessary process, sometimes. Bureaucratic theater, often. But either way, it's the reality of working inside a large organization. AI didn't change any of that. More output, same bandwidth Here's what actually happened. AI tools made the coding part faster. A task that took two hours of focused implementation might now take forty-five minutes with AI assistance. The code gets generated quicker. Boilerplate disappears. Scaffolding is instant. But the developer still has the same number of meetings. The same number of Jira tickets to update. The same cross-team dependencies to chase. The same architectural constraints to navigate. The same review process to follow. So what changed? The coding got faster, but the cognitive load got heavier. Because now that developer has to verify everything the AI produced. Line by line. Pattern by pattern. Edge case by edge case. AI doesn't produce code you can trust blindly. Anyone who has spent real time with these tools knows that. The output looks right. It often is right. But "often" is not "always," and the gap between those two words is where production incidents live. Reviewing AI-generated code is a specific kind of mental work. It's not the same as reviewing a colleague's pull request, where you can infer intent from their comments, their commit history, their known patterns. AI-generated code has no intent. It has statistical probability shaped into syntax. You can't ask it why it chose that approach. You can't assume it understood the business rule. You have to verify everything from scratch, every time. That verification requires deep focus. The same deep focus that was already scarce because of all the meetings and process overhead. The fatigue nobody talks about This is vibing fatigue. The accumulated cognitive cost of continuously generating, reviewing, validating, and correcting AI output while maintaining all the other responsibilities that never went away. It's not burnout in the traditional sense. It's a specific kind of mental exhaustion that comes from context-switching between trusting a tool and verifying that same tool, dozens of times a day. Generate. Read. Check. Fix. Generate again. Read again. Did it hallucinate a dependency? Did it introduce a subtle type mismatch? Did it follow the team's conventions or invent its own? Multiply that by eight hours. Subtract the three hours of meetings. Add the Slack interruptions. That's a normal Tuesday. And now your manager wants to know why your AI usage metric dipped this week. The irony is brutal. The developers who use AI most carefully, who take time to verify and validate, who refuse to blindly merge generated code, those developers will show lower throughput on the dashboards. They'll look less productive by the metric. The ones who accept everything Copilot suggests without reading it will look like stars. Until something breaks in production. Coding speed was never the problem Let me say this plainly because I think the industry needs to hear it. In large organizations, the primary constraint on software delivery is not how fast individual developers type code. It has never been that. The constraint is organizational. It's the time spent in alignment meetings. The weeks waiting for another team's API to be ready. The days lost to unclear requirements that should have been resolved before a single line was written. The review cycles. The compliance gates. The deployment windows. AI made one part of the pipeline faster. The part that was already the least broken. If you want to make engineering teams genuinely more productive, you don't measure how much AI they use. You measure how much uninterrupted focus time they have. You measure how many meetings could have been a document. You measure how long a pull request sits in review before someone looks at it. You measure the gap between "we decided to build this" and "a developer actually started building it." Those are the real bottlenecks. AI doesn't touch them. The mental cost of verification at scale There's a cognitive science angle here that the dashboards completely miss. Human attention is finite. Decision fatigue is real and well-documented. Every time a developer looks at AI-generated code and makes a judgment call about whether it's correct, safe, and aligned with the system's architecture, that's a decision. A non-trivial one. Now stack fifty of those decisions into a single afternoon, between a sprint retrospective and a cross-team sync. That's the actual developer experience in 2026. The tools got smarter. The models got better. The code quality improved. All true. But the human being reviewing that code has the same prefrontal cortex they had in 2023. Same working memory. Same attention span. Same vulnerability to fatigue, distraction, and the slow erosion of judgment that comes from sustained cognitive load. Companies that track AI usage as a KPI are optimizing for the machine side of the equation while completely ignoring the human side. They're measuring the accelerator without looking at the brake wear. What should be measured instead If you're a leader who genuinely wants to understand whether AI is helping your engineering teams, stop looking at adoption dashboards. Start looking at outcomes. Are defect rates going down? Is time-to-production shrinking? Are developers reporting that they have enough focus time? Is the ratio of coding time to process time improving? Are fewer incidents caused by subtle bugs that slipped through review? And the hardest question: are your developers telling you the truth about how they feel, or are they performing AI adoption because the dashboard is watching? I've seen teams where AI tools genuinely help. Where a senior developer uses them to eliminate tedious boilerplate and spends the recovered time on architecture, mentoring, and design. That's the ideal. That's what the sales pitch promised. But I've also seen teams where AI tools created a new layer of anxiety. Where developers feel pressure to accept suggestions they aren't sure about because the metric demands throughput. Where the cognitive load of constant verification is quietly eroding code quality in ways that won't show up until six months from now. The dashboard can't tell you which scenario you're in. Only your developers can. If you're willing to listen. The real productivity unlock You want a productivity win from AI in a large organization? Here's the actual play. Reduce the non-coding overhead first. Cut the meetings that don't produce decisions. Replace status updates with async documents. Give developers blocks of protected focus time. Fix the review bottleneck so pull requests don't age in a queue. Then let AI do what it does well within that recovered time. Let it handle boilerplate, generate tests, scaffold implementations. And give developers enough breathing room to actually verify the output properly. Not rushed between two meetings. Not with one eye on Slack. Properly. That's not a dashboard metric. That's an organizational design problem. It's harder to solve than buying licenses and tracking usage. But it's the only approach that actually works. The vibing will slow down Here's my prediction. The initial rush of AI-assisted coding is going to plateau. Not because the tools get worse. They'll keep getting better. But because the humans using them will hit a wall. The wall isn't technical. It's cognitive. There is a natural limit to how much AI output a single developer can responsibly verify in a day. We haven't mapped that limit yet because everyone is still in the honeymoon phase, generating code as fast as the model can produce it. When the fatigue sets in, and it will, the developers who last are the ones who learned to use AI selectively. Who figured out where the tool saves real time and where it creates hidden work. Who optimized for judgment, not throughput. The ones chasing usage metrics will burn out. Or worse, they'll keep going and stop verifying. And the codebase will pay for it later. That's vibing fatigue. Not a rejection of AI. A recognition that human attention is the actual scarce resource, and no model can substitute for it. Final Words I'd genuinely like to hear whether this matches your experience or if you see it differently. Agreement and disagreement are both welcome. You can find me on LinkedIn, X, and Threads if you want to continue the conversation there. If you're dealing with engineering productivity challenges, AI adoption strategy, or just want to talk through how your teams are actually using these tools, reach out. I'm always happy to talk shop. Are your teams tracking AI usage as a KPI? And if so, is anyone tracking the cognitive cost that comes with it? --- # Almost Solved Is the Most Dangerous Phase in Engineering URL: https://www.ivanturkovic.com/2026/03/31/ai-coding-almost-solved-most-dangerous-phase/ Published: 2026-03-31 Topics: AI, AI-Driven Development There is a growing conviction in the software industry that building software with AI is essentially a solved problem. Or if it is not solved today, it will be within a year. The reasoning goes something like this: a dwindling minority of naysayers at large, slow-moving companies are pretending AI is still bad, while a rapidly growing majority is adopting AI tools daily and actively improving their setups. The good ideas are sticking. The bad ones are fading. Momentum is undeniable. And the momentum part is true. It really is. According to Stack Overflow's 2025 Developer Survey, 84% of developers now use or plan to use AI tools in their development process, up from 76% the year before. Over half of professional developers use AI tools daily. GitHub Copilot alone has surpassed 20 million cumulative users. Reports suggest that around 42% of all committed code is now AI-assisted. By any adoption metric, this technology has crossed the threshold from experimental to standard practice. But here is where the narrative cracks. While adoption has surged, developer trust has moved in the opposite direction. In 2023 and 2024, over 70% of developers had a favorable view of AI tools. By 2025, that number dropped to 60%. Nearly half of developers now say they do not trust the accuracy of AI output, and only 3% report high trust. Among experienced developers, the skepticism is even sharper, with the lowest trust and highest distrust rates of any cohort. This is not a story about AI being bad. This is a story about the gap between "writes code fast" and "builds software that works." And that gap is where the real engineering begins. The 80/20 Trap Every experienced engineer knows the feeling. You are 80% done with a project, and someone asks when it will ship. You say it is almost done. Then the last 20% takes as long as the first 80%, sometimes longer. The integration points are wrong. The edge cases multiply. The thing that worked in isolation falls apart in context. AI-assisted software development is living in that last 20% right now. The first 80% has been genuinely impressive. AI can generate boilerplate at remarkable speed. It can scaffold components, write tests for well-defined functions, produce documentation, and autocomplete patterns that would take a developer minutes to type. For greenfield projects with clear specifications, it can feel almost magical. But the problems that remain are not incremental improvements on the same trajectory. They are categorically different problems, the kind that resist scaling through more compute or better models alone. This is important to understand. The narrative that AI coding is "almost solved" assumes we are on a smooth curve where each improvement brings us closer to completion. In reality, we hit a series of phase transitions where the nature of the challenge changes entirely. Writing code is one problem. Building software is a different one. Maintaining software is yet another. And coordinating multiple systems, teams, and priorities across time is another problem still. Each of those transitions introduces complexity that does not yield to the same solutions that handled the previous one. The Single-Thread Bottleneck Right now, most AI-assisted development workflows are single-threaded. A developer works with one AI agent, in one context window, on one task at a time. This is fine for many scenarios, and the productivity gains in this mode are well-documented. Developers report saving 30 to 60% of time on coding, testing, and documentation tasks when working with AI tools. The obvious next step is parallelism. If one AI agent can handle a task, why not deploy a swarm of agents to work on an entire feature set simultaneously? Multiple agents could architect, implement, test, and review code in parallel, collapsing weeks of work into hours. This is not hypothetical. Adrian Cockcroft demonstrated a five-agent swarm that produced over 150,000 lines of code in 48 hours. Frameworks like CrewAI, AutoGen, and OpenAI's Swarm are specifically designed for multi-agent orchestration. Anthropic's own Claude Code has shown feature flags for swarm capabilities. The tooling is emerging fast. But coordination remains the central challenge. Anyone who has managed a software team knows that adding more people does not automatically make things go faster. The same applies to AI agents, perhaps more so, because AI agents lack the social negotiation, shared context, and institutional knowledge that human teams develop over time. When you deploy multiple agents on a shared codebase, you immediately face questions about who owns which decision, how conflicts get resolved, and how architectural consistency is maintained across parallel work streams. A human team handles this through standup meetings, architectural reviews, shared conventions, and the accumulated trust of working together. AI agents handle it through structured handoff protocols and explicit context passing, which is to say, they handle it through the exact kind of careful specification that AI was supposed to save us from writing. The irony is worth sitting with. Multi-agent coordination requires precisely the kind of detailed, unambiguous specification that makes single-agent coding productive in the first place. You are not eliminating the specification problem. You are multiplying it. The Transparency Problem Here is a question that gets less attention than it deserves: how much of the AI-generated code in your codebase do you actually understand? One of the most pernicious effects of AI-assisted development is the ease with which generated code can drift away from the developer's mental model. The AI produces something that compiles, passes the tests you asked it to write, and appears to work. You accept it and move on. Repeat this a few hundred times and you have a codebase where significant portions were never deeply understood by anyone on the team. This is not a hypothetical risk. Sonar's 2025 State of Code report found that while developers estimate 42% of their committed code is AI-assisted, the overall proportion of time spent on low-value work has not meaningfully decreased. Instead, the nature of the work shifted. Developers spend less time writing boilerplate and more time validating AI suggestions. Less time drafting documentation and more time correcting generated code. The friction did not disappear. It redistributed. GitClear's analysis of over 153 million lines of code found that AI tools have driven code duplication up by four times. Short-term code churn is rising. These are not signs of a codebase getting cleaner. These are signs of a codebase accumulating complexity faster than the team can digest it. This is the transparency problem in its most personal form. When you stop understanding the code you are shipping, you lose something that no amount of tooling can give back: the ability to reason about your own system when things go wrong. The Assumption Gap Large language models are trained to be helpful. This is generally a good quality, except when helpfulness means making assumptions about what you want rather than asking clarifying questions about what you need. In a typical AI-assisted workflow, you describe a feature and the model generates an implementation. If you left out a critical design decision, perhaps about error handling, data validation, concurrency, or state management, the model will fill in the blanks with whatever pattern seems most common in its training data. It will not stop and say, "This is an important architectural choice. What is your preference?" Human developers, especially senior ones, do this all the time. They push back on vague requirements. They ask, "What happens when this fails?" They challenge assumptions before writing a single line of code. This adversarial thinking is one of the most valuable things a senior engineer brings to a team, and it is almost entirely absent from current AI tools. The result is software that works for the happy path and silently accumulates landmines in every direction the developer did not explicitly specify. Each unquestioned assumption is a decision made by a model that has no understanding of your business context, your risk tolerance, or your users. As I wrote in "The Future Engineer," the developers who will excel in this era are those who already know how to lead teams by asking the right questions and challenging specifications before work begins. That skill set transfers directly to AI management. The AI does not push back, so you must be even more rigorous in pushing forward. The Legacy Codebase Reality The greenfield advantage matters more than most AI advocates acknowledge. Starting a new project with a clear specification and no existing code is the best-case scenario for AI-assisted development. The context window is clean. There are no legacy patterns to navigate. There are no undocumented business rules hiding in a six-year-old service layer. Most real-world software development is not greenfield. Most of it is maintenance, modification, and extension of existing systems. These systems carry years of accumulated decisions, many of them undocumented, some of them actively contradictory. Understanding why a particular function exists in its current form often requires knowledge that lives nowhere in the codebase and everything in the heads of people who may have left the company years ago. AI tools struggle with this context. A 2025 report from Qodo found that 65% of developers using AI for refactoring and around 60% for testing, writing, or reviewing say the assistant "misses relevant context." Among developers who feel AI degrades code quality, 44% blame missing context. Even among those who think AI improves quality, 53% still want better contextual understanding. This is not a problem that scales smoothly with model size or context window length. The relevant context for modifying a line of legacy code might include a Slack conversation from 2019, a regulatory requirement that was never documented in code, and a production incident that reshaped the team's approach to a particular problem domain. No model, no matter how large its context window, will reliably surface this information without deliberate human effort to make it available. The gap between greenfield and legacy is where many of the "AI is basically solved" claims quietly collapse. The demos are always greenfield. The daily work is always legacy. The Guardrails Question One of the most underappreciated risks of AI-assisted development is the quiet deletion of safety invariants. An AI agent generates code, runs the tests, and everything passes. But what if the agent also modified the tests? What if it deleted a test that was enforcing an important constraint, not out of malice, but because the test conflicted with the generated implementation and removing it was the path of least resistance? This is not theoretical paranoia. CodeRabbit's analysis of 470 open-source GitHub pull requests found that AI-generated code contains 1.7 times more issues on average than human-written code. Critical issues were 1.4 times more frequent. Business logic errors appeared more than twice as often. Error handling gaps nearly doubled. Security findings were about 1.5 times more prevalent. The problem is not just that AI generates more bugs. The problem is that AI generates bugs in categories that are hardest to catch through automated testing: logic errors, incorrect sequencing, missing dependency handling, and concurrency misuse. These are the kinds of bugs that look correct in a code review, pass the tests, and then cause a production incident at 3 AM on a Saturday. When you combine this with the volume of code AI can produce, you get a math problem that does not work in anyone's favor. More code, each piece slightly more likely to contain subtle defects, reviewed by humans who are already feeling the fatigue of increased pull request volume. Google's 2025 DORA Report found that a 90% increase in AI adoption correlated with a 9% climb in bug rates, a 91% increase in code review time, and a 154% increase in pull request size. The guardrails question is really a governance question: who is responsible for ensuring that the invariants your system depends on are maintained as AI generates and modifies code at scale? The answer cannot be "the AI will check itself." It must be a deliberate architectural decision enforced through tooling, testing strategies, and human oversight. The Code Review Crisis Here is the problem that nobody has solved, and that I believe will define the next phase of software engineering: code review at the scale AI enables. Traditional code review works because a human reads the code, understands the intent, and evaluates whether the implementation correctly serves that intent. This process assumes that the volume of code in any given review is small enough for a human to meaningfully engage with it. AI breaks this assumption. When agents can produce thousands of lines in a session, when pull requests grow by 154%, when median PR size increases by 33%, the traditional line-by-line review model stops working. There simply is not enough human attention available to review code at the rate AI can produce it. The emerging alternative is to review intent rather than implementation. Instead of reading every line, you verify that the AI's output matches the specification, that the tests cover the right scenarios, that the architectural decisions align with the system's design principles. You move from reviewing code to reviewing outcomes. This is a profound shift and it introduces its own problems. Reviewing intent requires that the intent was clearly specified in the first place, which brings us back to the specification problem that AI was supposed to help us avoid. It also requires a level of systems thinking that not every developer has developed, because until now, they could build that understanding through the very act of writing and reviewing code line by line. Some teams are turning to AI-assisted code review tools to handle the volume. Platforms like CodeRabbit, Qodo, and Greptile are specifically designed to automate the first pass of review. But even these tools acknowledge that human review remains essential for architecture, business logic, and contextual decisions. The AI can catch syntax and pattern issues. It cannot tell you whether the feature should exist in the first place. The code review problem is a microcosm of the larger challenge. AI has massively increased the supply of code. It has not proportionally increased the capacity to evaluate, understand, and maintain that code. Until those two curves come into alignment, "almost solved" is a more dangerous state than "clearly unsolved." What Actually Gets Solved in a Year None of this means progress will stall. AI coding tools will improve substantially in the next twelve months. Context windows will grow. Multi-agent coordination will mature. AI-assisted code review will get meaningfully better. The tooling ecosystem will consolidate and professionalize. What will likely happen in a year is that the daily experience of working with AI on well-defined tasks will become noticeably smoother. The acceptance rate for AI suggestions will climb. The most common integration and testing patterns will become reliable enough that developers stop manually verifying them. For straightforward feature work on modern codebases, AI will handle an increasing share of the implementation with minimal human intervention. What will not be solved in a year is the set of problems that require judgment, context, and accountability. Specification clarity. Architectural coherence across large systems. Understanding legacy codebases that carry decades of institutional knowledge. Reviewing code at scale without losing the ability to reason about correctness. Maintaining invariants when the tool generating code has no concept of what those invariants mean. These are not engineering problems in the traditional sense. They are organizational, cognitive, and design problems. They require humans who understand not just how to write code, but why the code exists, what it protects, and what happens when it fails. The Engineer's Real Competitive Advantage If you are reading this and feeling anxious about AI making your skills irrelevant, I want to offer a different framing. The developers who thrive in this environment will not be the ones who can prompt an AI faster or who have memorized the latest model's capabilities. They will be the ones who can do the things AI cannot: define clear specifications, challenge assumptions, maintain architectural vision, understand business context, and take accountability for the systems they build. As I wrote in "The Eternal Promise," every wave of technology that promised to eliminate programmers instead created demand for more sophisticated developers working on more ambitious projects. COBOL was supposed to let business analysts write their own programs. Visual Basic was supposed to let anyone build applications. Low-code platforms were supposed to make professional developers optional. Each wave succeeded in making certain tasks easier while simultaneously raising the bar for what "building software" actually meant. AI is following the same pattern, but at a scale and speed that makes the transition more disorienting than previous waves. The implementation layer is being compressed. The layers above it, specification, architecture, integration, governance, are expanding. If you are investing your time in understanding systems deeply, in learning how to specify what software should do before thinking about how it does it, in developing the judgment to know when AI output is good enough and when it is hiding a subtle defect, you are building exactly the skills that will be most valuable as this technology matures. The developers at risk are not the ones at slow-adopting companies who have not yet integrated AI into their workflow. They are the ones who have adopted AI enthusiastically but without developing the complementary skills of verification, specification, and architectural thinking. Speed without judgment is not productivity. It is debt accumulation at scale. The Year Ahead The claim that AI coding will be "solved" in a year is not wrong because AI is incapable. It is wrong because it misidentifies what "solved" means. If "solved" means that AI can generate syntactically correct code for well-specified tasks on modern codebases, then yes, we are close. If "solved" means that AI can reliably build, maintain, and evolve complex software systems that serve real business needs across diverse organizational contexts, then we are further away than the adoption numbers suggest. The good news is that this gap represents an enormous opportunity. The tools are improving rapidly. The developers who learn to use them wisely, not just enthusiastically, will be capable of building things that were previously impossible at their scale and budget. The bad news is that the same gap is a trap for organizations that mistake tool adoption for capability. Deploying AI coding tools without investing in specification discipline, review governance, and architectural standards is like giving a team of junior developers unlimited compute and no senior oversight. You will get a lot of code, very fast, and you will spend the next three years untangling it. Almost solved is the most dangerous phase because it tempts you to stop doing the hard work of understanding what you are building and why. The engineers and organizations who resist that temptation will define the next era of software development. The ones who do not will become cautionary tales in someone else's blog post. Final Words Whether you agree with this analysis or find yourself firmly in the "it is basically solved" camp, I would love to hear your perspective. The most interesting conversations happen when people with different experiences compare notes. You can find me on LinkedIn, X, and Threads where I regularly share thoughts on software development, AI, and the evolving role of engineers. If you want to discuss something in more depth or explore how these ideas apply to your specific situation, feel free to reach out directly. What is the hardest unsolved problem in AI-assisted development from your experience? Is it code review at scale, legacy codebase understanding, multi-agent coordination, or something I did not cover? I want to know. --- # Complexity Is Never Eliminated. It Is Only Relocated. URL: https://www.ivanturkovic.com/2026/03/24/complexity-never-eliminated-only-relocated/ Published: 2026-03-24 Topics: AI, AI-Driven Development, Software Engineering When I published The Eternal Promise, tracing sixty years of attempts to eliminate programmers from COBOL through to today's AI coding tools, the post resonated far beyond what I expected. It reached the front page of Hacker News and generated hundreds of comments from engineers who recognized the pattern in their own careers. But one response in particular stopped me. A commenter articulated something I had been circling for years without ever naming precisely: Each abstraction layer does not eliminate complexity. It relocates it. That single sentence reframes the entire history of software development. It explains why every wave of "simplification" creates new specialists instead of eliminating old ones. It explains why AI-assisted coding feels simultaneously easier and harder. And it points to the most important, and most undervalued, skill in modern software engineering. The Conservation Law of Software In 1986, Fred Brooks published "No Silver Bullet," arguing that software development contains two kinds of complexity: essential and accidental. Essential complexity comes from the problem itself. If your banking system needs to handle thirty different regulatory requirements, that complexity must live somewhere in your software. Accidental complexity comes from the tools and implementation choices we make along the way. Brooks argued that most of the easy gains had already been captured by reducing accidental complexity through better languages, better tools, and better practices. The essential complexity, the hard part, remained stubbornly resistant to any single technological breakthrough. There would be no silver bullet. What Brooks described conceptually, Larry Tesler formalized into what became known as Tesler's Law, sometimes called the Law of Conservation of Complexity. Tesler, who worked at Xerox PARC and Apple, observed that every system has an inherent amount of complexity that cannot be removed. It can only be shifted. If you make one part of a system simpler, some other part absorbs the complexity you displaced. Larry Wall, the creator of Perl, independently arrived at the same insight and called it the Waterbed Theory of Complexity. Push down on a waterbed in one place and it bulges up somewhere else. The water, the complexity, is conserved. It just moves. This is not an abstract theoretical observation. It is the precise mechanism that explains every cycle of technological promise and partial delivery in the history of software. And it is the mechanism operating right now with AI-assisted coding. A History of Relocations Once you see the relocation pattern, you cannot unsee it. Assembly language was hard because you managed machine instructions, registers, and memory addresses directly. High-level languages like FORTRAN and COBOL relocated that complexity. Developers no longer thought in machine instructions. Instead, they thought in business logic, data structures, and control flow. The simple cases became dramatically simpler. But the complexity did not disappear. It moved to a new boundary: specifying correct business logic in a language that was more forgiving syntactically but just as demanding logically. An entire generation of COBOL programmers emerged as specialists at that new boundary. Fourth-generation languages in the 1980s relocated complexity again. Instead of writing procedural code, developers worked with higher-level constructs for database queries, report generation, and screen design. The simple cases became simpler still. But the complexity accumulated at data modeling, integration, and performance optimization. When business logic exceeded what the 4GL could express cleanly, developers hit walls that required deeper skills than before, not shallower ones. CASE tools promised to relocate complexity from code to diagrams. Visual modeling would replace textual programming. The simple cases worked. Complex systems generated code that was inefficient, difficult to customize, and nearly impossible to debug. The complexity relocated from writing code to understanding and correcting generated code. Sound familiar? Object-oriented programming relocated complexity from procedure management to class design and inheritance hierarchies. Web frameworks relocated it from HTTP handling to configuration and convention. Microservices relocated it from monolithic codebases to network communication and distributed systems management. Cloud computing relocated it from hardware to configuration management. Each relocation was genuinely valuable. Each made the simple cases dramatically simpler. And each created a new class of specialists to manage the complexity that accumulated at the new boundary. Where AI Relocates Complexity LLMs are following the same pattern, but the destination of the relocation has implications that most people have not fully absorbed. With previous abstractions, the complexity moved laterally, from one form of creation to another form of creation. From machine instructions to business logic. From procedural code to data models. From code to configuration. The nature of the work changed, but the fundamental activity remained constructive. You were still building something. With AI-generated code, the complexity is relocating from a constructive activity (writing code) to an evaluative activity (verifying code). This is a categorically different kind of work. It requires different skills, different mental models, and different organizational structures. When you write code yourself, you understand it because you built it. The decisions are yours. The assumptions are visible to you because you made them. When something breaks, you have a mental model of why it might have broken because you assembled the system piece by piece. When you verify AI-generated code, you are auditing decisions made by a system whose reasoning you cannot inspect. You are reading code shaped by assumptions you did not make, in a structure you did not design, reflecting patterns from training data rather than from your architectural intent. The verification problem is not just "does this code work?" It is "does this code work for the right reasons, handle the edge cases I care about, maintain the invariants I depend on, and avoid introducing subtle defects that will surface in production three months from now?" That is a fundamentally harder question than "can I write code that works?" The Verification Paradox Here is where the data tells a story that should concern everyone. Sonar's 2025 State of Code report surveyed over 1,100 developers globally and found that 96% do not fully trust that AI-generated code is functionally correct. Only 48% always check their AI-assisted code before committing it. That gap, between near-universal distrust and barely half-hearted verification, is what Sonar calls "verification debt." Amazon CTO Werner Vogels captured this shift precisely at AWS re:Invent 2025: "Now, the world is changing. You will write less code, because generation is so fast. You will review more code because understanding it takes time." The numbers support his claim. 38% of developers say reviewing AI-generated code requires more effort than reviewing code written by human colleagues. 66% report spending significant time fixing AI code that is "almost right but not quite." 61% agree that AI often produces code that looks correct but is not reliable. This is the verification paradox. AI makes generating code nearly effortless, so developers generate more of it, but their capacity to verify code has not increased at all. In fact, it may have decreased, because verifying code you did not write is inherently harder than verifying code you did, and because the sheer volume of generated code exceeds any individual's review bandwidth. The complexity did not disappear. It relocated from generation to verification. And verification, it turns out, is harder. Why Verification Is Harder Than Writing This claim deserves examination because it sounds counterintuitive. How can checking code be harder than creating it? Consider what happens when you write code from scratch. You start with a problem. You decompose it into smaller problems. You make explicit decisions about data structures, algorithms, error handling, and edge cases. Each decision builds on the ones before it. By the time you have working code, you also have a mental model of every significant choice embedded in that code. Verification is almost free because understanding is a byproduct of creation. Now consider what happens when you verify AI-generated code. You start with output. You must reverse-engineer the decisions the model made. Why this data structure? What edge cases does it handle? What edge cases does it miss? Does the error handling match the failure modes of this specific system? Are the concurrency assumptions safe in this particular deployment context? Every question requires you to understand something about the code that you did not decide, and therefore do not inherently know. You have to develop the same depth of understanding you would get from writing the code, but without the benefit of having written it. In some ways, this is like reviewing a colleague's code, but with a crucial difference: your colleague can explain their reasoning when asked. The AI cannot. CodeRabbit's analysis of 470 GitHub pull requests found that AI-generated code produces 1.7 times more issues per review, with business logic errors appearing at twice the rate and error handling gaps nearly doubling. But the more revealing finding is about the types of issues. The largest relative increases appeared in readability, naming consistency, and structural quality: categories that increase cognitive load for reviewers and make verification harder over time. The code does not just have more bugs. It is harder to audit. The complexity has relocated to a boundary that demands skills the industry has historically undervalued. The Auditor Problem Programming culture has always celebrated creation over evaluation. The developer who ships a feature gets more recognition than the one who catches a critical bug during code review. Writing code is glamorous. Reading code is grunt work. Building is fun. Reviewing is tedious. This cultural bias has practical consequences in the AI era because the skill set required for effective verification of AI-generated code is closer to financial auditing than to traditional software development. A financial auditor does not rebuild the company's accounting system from scratch to determine whether it is correct. They examine the output, trace transactions through the system, test assumptions against reality, and identify discrepancies between what the system claims and what actually happened. They work backwards from results to reasoning. They look for patterns that indicate systemic problems rather than inspecting every individual entry. Verifying AI-generated code requires the same approach. You cannot rewrite every function to confirm it is correct; that defeats the purpose of using AI. Instead, you must develop the ability to read code critically, identify patterns that indicate likely problems, trace logic through components you did not design, and evaluate whether the system's behavior matches its specification. This is auditing. And auditing is a discipline with its own methodologies, its own skill development path, and its own career structure that the software industry has barely begun to develop. The irony runs deep. For decades, the software industry has tried to make development more like engineering: rigorous, measurable, predictable. AI may finally achieve that goal, but not in the way anyone expected. The rigorous discipline it demands is not engineering. It is audit. Tesler's Law in Action: The Specifics Let me trace the complexity relocation with concrete specificity, because the pattern becomes more striking the closer you look. Before AI, a developer building an authentication system would make a series of deliberate choices. How to hash passwords. Whether to use bcrypt or argon2. What salt length to use. How to handle session tokens. How to manage token expiration. How to implement rate limiting on login attempts. Each choice required research, understanding, and explicit decision-making. With AI, the developer describes "build an authentication system" and receives an implementation. The complexity of choosing bcrypt versus argon2 did not disappear. It relocated from a conscious design decision to a verification question: did the AI make a safe choice? The developer still needs to know enough about password hashing to evaluate the output. They also need to know enough to recognize when the AI made an insecure choice, like using SHA-256 for password hashing, which will look correct to anyone who does not understand why it is wrong. The essential complexity, choosing a secure hashing algorithm for this specific threat model, is conserved. It moved from the writing boundary to the verification boundary. And at the verification boundary, the failure mode is worse: instead of a developer who explicitly chose the wrong algorithm (and might catch the mistake during implementation), you have a developer who accepted the wrong algorithm without realizing a decision was made at all. This pattern repeats across every category of technical decision. Error handling, concurrency models, data validation, API design, security boundaries, performance characteristics. Each one represents essential complexity that AI does not eliminate but relocates from explicit design decisions to implicit verification challenges. The New Specialist Every previous relocation of complexity created a new class of specialist. Assembly gave way to COBOL programmers. COBOL gave way to database administrators when 4GLs pushed complexity to data modeling. Web frameworks created a generation of DevOps engineers when complexity accumulated at deployment and operations. Cloud computing created infrastructure architects. Microservices created platform engineers. AI is creating the need for a specialist that does not yet have a widely recognized name. The closest existing role is "code reviewer," but that understates the scope. The specialist AI demands is someone who can evaluate code they did not write, against specifications they must verify independently, in systems whose architecture was shaped by decisions they must reconstruct, for correctness criteria that span functional behavior, security properties, performance characteristics, and long-term maintainability. This is not a junior role. It requires deep technical expertise, broad systems knowledge, and the kind of judgment that comes from years of building and debugging production systems. You cannot effectively audit code if you have never built a similar system yourself, because you would not know what to look for. Here is the uncomfortable truth: the developers best positioned to verify AI-generated code are experienced senior engineers. The ones who have built enough systems to recognize patterns, the ones who have debugged enough production incidents to know where subtle bugs hide, the ones who have enough architectural judgment to evaluate whether a design choice is sound even when it compiles and passes tests. These are exactly the developers the industry periodically claims AI will make redundant. In reality, AI has made their skills more valuable, not less. It has just relocated the application of those skills from construction to verification. What This Means for the Industry The complexity relocation framework has practical implications for how organizations should be thinking about AI adoption. First, investing in AI coding tools without equally investing in verification practices is like installing a high-speed assembly line without quality control. You will produce more, faster. Much of what you produce will need to be caught, corrected, or recalled. The savings from faster generation will be consumed by the costs of insufficient verification. Sonar's finding that only 48% of developers always check AI code before committing is an early warning sign of verification debt accumulating across the industry. Second, the skills that matter most in the AI era are not prompt engineering skills. They are verification skills. The ability to read code critically. The ability to identify security antipatterns. The ability to recognize architectural decisions that will cause problems at scale. The ability to evaluate whether a test suite actually tests the things that matter, or merely tests the things that are easy to test. Organizations that train developers to generate code faster without training them to evaluate code more rigorously are building a capability imbalance that will eventually collapse. Third, the role of senior engineers must evolve to emphasize review, mentoring, and architectural oversight rather than raw implementation output. If AI handles the construction, then human expertise must concentrate at the verification boundary, which is where the relocated complexity lives. This means valuing code review as a first-class engineering activity rather than a chore that delays shipping. Fourth, tooling must evolve. AI-assisted code review tools are emerging, and they will help with the volume problem. But they face the same limitation as any verification tool: they can catch pattern-level issues but they cannot evaluate whether the code serves the business intent. That judgment requires humans. The optimal workflow is likely AI generating code, AI performing first-pass review for patterns and common vulnerabilities, and humans performing deep review for business logic, architectural coherence, and specification compliance. The Pattern Will Repeat If history is any guide, the verification boundary will not remain the final destination. Some future technology will relocate complexity from verification to whatever lies beyond it. Perhaps formal verification tools will become practical enough to automate much of the auditing. Perhaps AI systems will become reliable enough that statistical verification, sampling and testing rather than comprehensive review, becomes an accepted practice. When that happens, the complexity will not disappear. It will relocate again. A new boundary will emerge, a new class of specialist will be needed, and a new generation of commentators will announce that the previous generation of specialists is finally, truly, definitively obsolete. They will be wrong, for exactly the same reason every previous prediction was wrong. The complexity is conserved. It is only relocated. And wherever it lands, it demands human judgment to manage. Fred Brooks saw this forty years ago. Larry Tesler saw it at Xerox PARC. Every experienced engineer who has lived through a technology transition has felt it, even if they could not name it precisely. Now the pattern has a name, and a mechanism, and a sixty-year history of evidence. The complexity is never eliminated. It is only relocated. And the engineers who understand where it is going will always be the ones who matter most. Final Words Whether you have experienced this complexity relocation firsthand or see it differently, I would genuinely like to hear your perspective. The best insights come from people comparing notes across different environments and career stages. You can find me on LinkedIn, X, and Threads where I regularly share thoughts on software development, AI, and the evolving role of engineers. If you want to discuss something in more depth or explore how these ideas apply to your specific context, feel free to reach out directly. Where has complexity relocated in your world? Has AI moved the hard part of your work from building to verifying, or is it landing somewhere else entirely? I want to know. --- # “Done” Is Not Merge: Why Your Definition of Done Is Probably a Lie URL: https://www.ivanturkovic.com/2026/03/18/done-is-not-merge/ Published: 2026-03-18 Topics: Software Engineering The most dangerous lie in software engineering is a two-word status update: "It's done." In most teams, done means the pull request was merged. The CI pipeline turned green. The ticket moved to the right column on the board. Someone wrote "Done" in Slack, maybe with a checkmark emoji for extra confidence. And then everyone moved on to the next thing. But merging code into a main branch is not shipping software. It is the beginning of a process that too many teams treat as the end. And this confusion, this quiet redefinition of what "done" means, is responsible for more production incidents, more midnight pages, more customer trust erosion, and more burned-out engineers than almost any other cultural failure in our industry. Done is not merge. Done is tested, deployed, and stable in production. That distinction sounds simple. It is not. Getting it right requires rethinking how teams work, what they measure, what they reward, and what they are willing to slow down for. Let me explain why. The Merge Illusion There is a specific moment in the software development lifecycle that feels like completion but is not. It is the moment a pull request gets approved, the last comment is resolved, the branch merges cleanly, and the CI checks pass. There is even a satisfying visual cue: the merge button turns green, the branch disappears, and the commit lands in main. Psychologically, this feels like closure. The problem is solved. The feature is built. The work is finished. Except nothing has actually happened yet. Not for the user. Not for the business. Not for the infrastructure that will bear the weight of this change under real traffic, real data, and real edge cases that no test environment can perfectly simulate. What happened is that a set of changes entered a shared codebase. That is it. Those changes have not been deployed. They have not been observed under production conditions. They have not survived their first hour of real-world usage. And until they do, calling them "done" is, at best, premature optimism and, at worst, organizational self-deception. I have seen this pattern play out across two decades of building systems in fintech, payments, and high-traffic platforms. A team merges a feature on Friday afternoon. Everyone marks their tickets as complete. The sprint velocity looks great. Then Monday morning arrives with a flood of error alerts because the change interacted with a production data pattern that did not exist in staging. The feature was "done" for three days before anyone realized it was broken. The merge illusion is not a tooling problem. It is a cultural one. Teams treat the merge as the finish line because that is what their processes implicitly reward. Sprint reviews celebrate merged stories. Velocity metrics count points when tickets move to "Done." Performance reviews highlight features shipped, where "shipped" quietly means "merged." The entire incentive structure points at the wrong moment in the pipeline. Why This Matters More Than You Think If you are building a side project or a prototype, the distinction between merged and deployed barely matters. But the moment real users depend on your software, the moment money moves through your system, the moment uptime is not optional, the gap between "merged" and "production-stable" becomes the most important distance in your entire engineering organization. The 2024 Accelerate State of DevOps Report from Google's DORA team tells a revealing story about this gap. Their research, drawn from thousands of technology professionals worldwide, found that elite-performing teams deploy 182 times more frequently than low performers while maintaining eight times lower change failure rates. That is not a typo. The teams that ship the most also break production the least. And the core reason is that elite teams do not consider work done at merge. They consider work done when it is verified in production. The DORA research introduced a fifth metric in 2024: deployment rework rate, measuring the percentage of deployments that are unplanned work to fix bugs. This metric exists precisely because the industry needed a way to quantify the cost of calling things "done" too early. When teams merge without verifying production stability, they create rework. Rework consumes capacity that should go toward new value. Rework demoralizes engineers who thought they were finished. Rework erodes customer trust one incident at a time. Consider the CrowdStrike incident in July 2024. A routine update, presumably tested and approved through internal review processes, was deployed and caused failures across 8.5 million Windows devices globally. The estimated financial damage exceeded three billion dollars. Banking, healthcare, and transportation sectors were paralyzed for up to 72 hours. The update had been "done" by every internal definition before it reached production. Production disagreed. That is an extreme example, but the pattern scales down to every team that has ever merged a database migration that works perfectly in staging and locks a production table for forty minutes. Or a caching change that behaves correctly under test load but causes a thundering herd under real traffic patterns. Or an API change that passes all contract tests but breaks a mobile client that was not included in the test matrix. The gap between merge and production stability is where most real damage happens. And it happens quietly, repeatedly, across thousands of teams every single day. The Anatomy of Actually Done If "done" is not merge, then what is it? Let me be specific. A piece of work is done when all of the following are true, and not before. The code has been reviewed and merged. This is step one, not the final step. Code review catches logical errors, design problems, and style inconsistencies. It is necessary. It is nowhere near sufficient. Automated tests pass in the CI pipeline. Unit tests, integration tests, and whatever automated checks your pipeline includes should all pass. But teams that stop here are confusing "the code compiles and tests pass" with "the software works." These are different claims about different things. The change has been deployed to a production-equivalent environment. Staging environments that do not mirror production data volumes, traffic patterns, third-party service behaviors, and infrastructure configurations are not production-equivalent. They are approximations. Useful approximations, but approximations nonetheless. The deployment to production has been completed. The code is running on production infrastructure, serving real traffic. Not waiting in a release queue. Not behind a feature flag that nobody has toggled. Not in a canary that has not been promoted. Actually deployed. Actually running. Post-deployment verification has been performed. Someone has confirmed that the change behaves as expected in production. This might mean checking dashboards, reviewing error rates, verifying key user flows, or confirming that monitoring alerts have not fired. The method matters less than the fact that verification happened. The change is stable in production over a meaningful observation window. This is the part almost everyone skips. A change that works for the first five minutes might fail under overnight batch processing, peak traffic hours, or end-of-month reconciliation jobs. Stability is not a point-in-time check. It is a window of observed behavior. That window might be an hour for a cosmetic change or a full business cycle for a payment processing modification. The duration should be proportional to the risk. Only after all six conditions are met should a ticket be marked as done. Only then should it count toward velocity. Only then should an engineer mentally move on to the next task. Everything before that final confirmation is work in progress, regardless of what column it sits in on your project board. The Cultural Debt Behind the Wrong Definition If the correct definition of done is so straightforward, why do most teams get it wrong? Because getting it right is culturally expensive. Expanding the definition of done from "merged" to "stable in production" has consequences that ripple through the entire organization. Sprint velocity goes down, at least on paper, because work takes longer to complete by the new definition. Stakeholders who are used to seeing 30 story points per sprint now see 20. Product managers who promised a feature by a certain date realize the feature is not done when the last PR merges but when production confirms it works. Release planning gets harder because you cannot predict exactly when production stability will be confirmed. These are real costs. And in organizations that optimize for the appearance of speed over actual delivery, the honest definition of done loses every time. It is much easier to point at a merged PR and say "we shipped it" than to admit that shipping requires another day of observation and verification. There is also an ego component. Engineers naturally want to feel the satisfaction of completion. The moment a PR merges, there is a dopamine hit of closure. Asking someone to hold off on that satisfaction, to keep the ticket open, to stay mentally engaged with a change through deployment and observation, requires a discipline that runs counter to how our brains process accomplishment. We are wired to close loops, and a merged PR feels like a closed loop even when it is not. Then there is the tooling problem. Most project management tools make it trivially easy to move a ticket to "Done" but surprisingly hard to create a workflow that enforces post-deployment verification. Jira, Linear, and similar tools are designed around the concept of developer handoff, not production confirmation. Adding a "Verify in Production" column is easy. Enforcing that it is not skipped requires cultural commitment that no tool can automate. The result is what I call definition-of-done debt. Teams carry an ever-growing gap between what they claim is done and what is actually stable in production. This debt compounds exactly like technical debt. Small at first, invisible to management, and eventually catastrophic when the accumulated lies collide with reality during a critical outage. What the DORA Data Actually Tells Us The 2024 DORA research offers some counterintuitive findings that directly relate to the definition of done problem. For the second consecutive year, the data shows that AI tooling correlates with worsened software delivery performance at the team level. Teams using AI code generation tools are writing more code faster, but that speed is not translating into better delivery outcomes. In many cases, the opposite is happening. This finding makes perfect sense through the lens of the definition of done. AI tools accelerate the journey to merge. They help write code faster, generate tests faster, resolve review comments faster. But they do nothing to accelerate the journey from merge to production stability. If anything, they make that journey more treacherous because more code is being merged more quickly with less time for human consideration of production implications. The DORA data also found that the high-performance cluster shrank from 31% of respondents in 2023 to just 22% in 2024, while the low-performance cluster grew from 17% to 25%. Software delivery performance is getting worse for most teams, not better. This is happening during a period of unprecedented tooling improvement, faster CI/CD pipelines, and widespread adoption of deployment automation. The obvious question is why. And the answer, I believe, is that the industry is optimizing the wrong phase of the delivery pipeline. We have invested enormously in making the path from idea to merge faster and smoother. We have invested comparatively little in making the path from merge to production stability reliable and disciplined. The tools are better than ever. The definition of done has never been worse. Consider the change failure rate metric specifically. DORA found that for elite teams, the rate sits between 0% and 15%. For low-performing teams, it can exceed 64%. The difference is not that elite teams write better code. It is that elite teams have processes that catch problems before and immediately after production deployment. They do not declare victory at merge. The Staging Trap I need to address a common objection here. Many teams will say: "We do not consider work done at merge. We consider it done when it passes staging." And while that is better, it is still not enough. Staging environments are necessary. They are not sufficient. Every experienced engineer has a collection of stories about things that worked perfectly in staging and failed immediately in production. The reasons are well understood but persistently underestimated. Staging databases are smaller. They contain test data with predictable distributions, not the chaotic accumulation of years of real user behavior. That query that runs in 50 milliseconds against a staging database with 100,000 rows might run for 45 seconds against a production database with 80 million rows and fragmented indexes. Staging traffic patterns are artificial. Real traffic has spikes, seasonal patterns, geographic distributions, and concurrency profiles that synthetic load testing only approximates. A connection pool that handles staging load comfortably might exhaust under production concurrency. A rate limiter that seems well-tuned against simulated requests might throttle legitimate users during a flash sale. Third-party services behave differently in staging. Sandbox APIs from payment providers, email services, and identity platforms do not replicate the latency, error rates, and timeout behaviors of their production counterparts. An integration that appears bulletproof in staging might fail under the specific network conditions and rate limits of production third-party endpoints. Staging is a rehearsal. Production is the performance. And anyone who has ever performed anything knows that rehearsals and performances are fundamentally different experiences. The audience changes everything. How to Actually Fix This Fixing the definition of done is not a tooling change or a process tweak. It is a cultural shift that requires commitment at every level of the engineering organization. Here is what it looks like in practice. Redefine "Done" explicitly and make it visible. Write down what done means for your team. Print it on the wall if you have to. Make it part of your onboarding documentation. The definition should include production deployment and a stability observation window. Every new team member should understand on day one that merged is not done. Change what you measure. Stop measuring velocity in terms of merged stories. Start measuring it in terms of changes verified stable in production. This single change in measurement will transform how your team thinks about completeness. When the scorecard rewards production stability, people will optimize for production stability. Make deployment a developer responsibility, not an ops handoff. The person who writes the code should be involved in deploying it and verifying it works. This is not about eliminating platform teams or DevOps roles. It is about ensuring that the engineer who understands the change most deeply is the one confirming its production behavior. When deployment is someone else's problem, production verification becomes no one's responsibility. Build post-deployment verification into your pipeline. Automated smoke tests that run after deployment. Dashboard checks that compare pre-deployment and post-deployment metrics. Alerting rules that are sensitive to the specific changes being deployed. These do not replace human judgment, but they create a minimum baseline of production verification that happens every single time, even on Friday afternoons when everyone wants to go home. Institute a stability observation window. Define how long a change needs to run in production before it is considered done. For low-risk cosmetic changes, this might be an hour. For payment processing changes, it might be a full business day or a complete transaction cycle. The window should be proportional to the blast radius of a potential failure. Document these windows and enforce them. Add a "Verify in Production" step to your workflow. This should be a mandatory column on your project board that sits between "Deployed" and "Done." Tickets cannot move to "Done" until someone has actively verified production behavior and confirmed stability within the defined observation window. Automate the verification where possible, but do not let automation substitute for intentional human confirmation on high-risk changes. Make rollback a first-class citizen. If your deployment process does not support fast, reliable rollback, your definition of done is built on a foundation of hope. Every deployment should have a clear rollback plan. Every engineer should know how to execute it. And the time to test rollback procedures is before you need them at 2 AM, not during the incident. Protect the time required for verification. This is the hardest part. Product managers will push for the next feature. Sprint planning will try to pack every available hour. The pressure to move on to the next ticket immediately after merge is constant and relentless. Leadership must create space for post-deployment verification and treat it as an essential part of delivery, not as overhead that can be skipped when things get busy. The AI Acceleration Problem This issue is becoming more urgent, not less, because AI-assisted development is dramatically increasing the speed at which code reaches the merge point. When developers can generate implementations faster, write tests faster, and resolve code review feedback faster, the merge rate accelerates. But the laws of production do not accelerate with it. Production databases do not process queries faster because AI wrote the code. Network latency does not shrink because Copilot generated the API call. Third-party rate limits do not increase because the integration was built in half the time. The time between merge and production stability is governed by physical and operational constraints that AI cannot compress. This creates a dangerous bottleneck. Code enters the pipeline faster than it can be verified in production. The backlog shifts from "waiting to be built" to "waiting to be confirmed stable." And if teams respond to this bottleneck by relaxing their verification standards, because the code was AI-generated and the tests all pass, they trade visible speed for invisible risk. The 2024 DORA finding that AI tools correlate with worse delivery performance is not a condemnation of AI. It is a warning that accelerating one phase of the pipeline without proportionally investing in the phases that follow creates imbalance. And imbalanced pipelines produce failures. If you are using AI to write code faster, you should be investing equally in deploying and verifying faster. Otherwise, you are just filling a funnel at the top while the bottom stays the same size. The overflow is production incidents. The Economics of the Real Definition I understand the reluctance to adopt a stricter definition of done. It looks slower on paper. It produces lower velocity numbers. It makes sprint planning less predictable. These are legitimate business concerns. But consider the economics from the other direction. Every production incident caused by a change that was "done" but not verified consumes engineering time for investigation and remediation. It often requires urgent communication with affected customers. It may trigger SLA penalties. It erodes user trust in ways that are difficult to quantify but very real. It creates context-switching costs as engineers are pulled from current work to fix past work they thought was finished. And it generates rework, the newly measurable metric that DORA added precisely because it is so expensive. In my experience building payment systems where a production bug can mean real money moving to the wrong place, the cost of a single unverified deployment easily exceeds the cumulative cost of a hundred post-deployment verification cycles. The math is not close. Spending thirty minutes verifying a deployment is not overhead. It is insurance with an extraordinary return on investment. The teams I have seen operate most effectively are the ones that understood this math early. They appear slower in sprint velocity metrics. They are dramatically faster in time-to-value metrics because their features actually work when they reach users, and they spend almost no time on incident response and rework. Their "slow" is faster than most teams' "fast" because their fast includes the hidden cost of cleaning up after premature declarations of done. A Different Relationship With Completion At its core, this is about developing a different relationship with the idea of completion. In a world that celebrates shipping speed above all else, it takes discipline to say: "This is not done yet. It is merged. It is deployed. But I have not confirmed it is stable." That discipline is what separates professional software engineering from hobbyist coding. In a hobby project, merged really is done because there are no users depending on stability, no SLAs to honor, no revenue at risk. But the moment software enters production at scale, the definition of done must expand to include the reality that production is a different environment with different constraints, different failure modes, and different consequences. The best engineers I have worked with over two decades share this trait. They do not relax after the merge. They do not mentally close the loop when the CI turns green. They carry a low-level awareness that the work is not finished until production confirms it. They check dashboards after deployment without being asked. They set personal reminders to verify stability the next morning. They treat the observation window as part of the job, not as extra credit. This is not paranoia. It is professionalism. A surgeon does not declare a procedure complete the moment the incision is closed. There is a recovery period, a monitoring phase, a follow-up. Software in production deserves the same rigor. The code is not the product. The running, stable, verified system is the product. Everything else is work in progress. The Definition That Earns Trust When a team adopts the real definition of done, something remarkable happens. Product managers start trusting engineering timelines because "done" stops being followed by production incidents. Customers stop encountering bugs that should have been caught before they affected anyone. Oncall rotations become calmer because fewer changes reach production without verification. Engineers spend less time fighting fires and more time building things that matter. Trust is the real output of a correct definition of done. Not velocity. Not story points. Not the number of PRs merged per sprint. Trust. Trust from your users that the software works. Trust from your business that engineering delivers what it promises. Trust from your team that when someone says "it is done," they mean it. That trust is worth more than any velocity metric. And it starts with the decision to stop lying to yourself about what "done" means. Done is not merge. Done is not a green CI pipeline. Done is not a ticket in the right column. Done is tested, deployed, and stable in production. That is the only definition that matters. Everything else is a comfortable fiction that production will eventually expose. Final Thoughts IIf this resonated with you, or even if you disagree, I would love to hear your take. The best conversations happen when people with real experience push back, share their own observations, and add nuance to the discussion. You can follow me on LinkedIn for regular thoughts on software engineering, AI, and the realities of building technology over the long term. I also post on X and Threads for shorter, sharper takes. If you want to discuss this topic further, collaborate on something, or just say hello, feel free to reach out. I respond to everything. What do you think? Does your team consider work done at merge, at deployment, or only after confirmed production stability? What challenges have you faced in moving the definition further right? Drop me a message. I am genuinely curious what you are seeing from where you sit. --- # AI Made Learning Feel Pointless. That’s Exactly When It Matters Most. URL: https://www.ivanturkovic.com/2026/03/06/ai-made-learning-feel-pointless/ Published: 2026-03-06 Topics: AI, Software Engineering A week ago my blog post got noticed by developers. I got so many messages and there was a recurring theme. They feel helpless and anxious with the ever growing AI dominance in writing code that might not be perfect but it is almost good enough in many cases. But one question stood out more than others. "What's the point?" of continuing to practice and learn the skills. As we know most engineers pride themselves in their work. In their free time they spend reading tech books, attend meetups. It is not just a job for them, it is their lifestyle. Almost a purpose of living. I didn't have a real answer until today and I will try to write it. Twenty years. I spent more than that since I got my first real work as a developer. I picked up and forgotten countless programming languages until I settled on a few that I still use to this day. New shiny frameworks that felt so futuristic yet so many are forgotten right now and only a few developers are still left maintaining the legacy code that used them. There were some paradigms that rewired how my brain works and writes code and until this day I use them. Some of it mattered. Some of it was just restlessness. Hard to tell which was which at the time. Now AI writes the code. Good code. Not always great, but good enough in many cases and getting better faster than most people want to admit. So that question hit me differently than the usual career panic I was reading about. "What's the point" is not really about employment. It is about identity. Engineers who spend their weekends on tech books and show up to meetups after a full work day are not doing it for a raise. They do it because it is who they are. That is the part nobody in these AI conversations actually talks about. I sat with it for a while before I had anything honest to write back. Through time I worked on so many hard things. Some were just hard at that time and now I laugh at how trivial those problems were. But after finishing them I was changed in a way that had nothing to do with the skill itself. Some projects took months of my life. I spent a long stretch buried in distributed systems, not just reading and researching but building, breaking, retrying, trying to understand why it does not work. It was not just consistency, replication or failure handling. I learned more about trust. About how two people can look at the same problem and see completely different realities. That way of thinking did not stay in the code. It got ingrained. It showed up in business decisions, in conversations, in moments that had nothing to do with software at all. As years passed I kept noticing the same thing. The details of the problem fade. The way of seeing sticks. Every engineer who has wrestled with genuinely complex problems for years develops a patience with complexity that is hard to explain to people who have not done it. We stopped expecting clean fast solutions. We started asking the question behind the question. Like chess where you are already thinking several moves ahead before you touch a piece. That is not a technical skill anymore. It is just thinking better than you did before. That is the part people are actually missing when they feel the panic. All the late nights, the wrong fixes, the frustration, the soul draining periods where nothing works. And then suddenly it does. Your efforts did it. You feel relieved, even proud, like you did something nobody else had done in quite that way before. That feeling does not just make you a better programmer. It makes you more logical. More patient. More human in a way that has nothing to do with your job or your salary. But only if the learning is real. A real feeling, going through the whole process, being genuinely lost, sometimes throwing everything away and starting from scratch. That kind of practice is something most people almost never get. AI does not take it away. If anything it makes it clearer. The routine work goes to the model. What is left is judgment. Knowing when something subtly will not work. Knowing what question to ask before a single line gets written. That only comes from years of real struggle with problems that had no obvious answer. Without the learning, that judgment does not develop. And without the judgment, you are just a prompt. The developer who asked me "what's the point" is worried about the wrong thing. The code was never the point. The point was what learning to write it properly did to how you think about everything else. AI raises the floor. The ceiling is still yours to reach. And the people who spent years actually learning, not just using, are rarer now. Not less valuable. More. Do I think solving hard technical problems has some higher altruistic meaning? I absolutely do not. But I do think the mental training we go through every day doing it can get us there. Not because the problems themselves matter that much. Because the discipline of being wrong, rebuilding, persisting, and staying honest about what you do not know yet, that builds something. Something that reaches past the code, past the career, past the industry entirely. That might actually be the point. Final Words This one is more personal for me than most things I write and I genuinely do not know if I got the answer right. Agree with it, push back on it, tell me I am missing something. All of it welcome. You can find me on LinkedIn, X, and Threads. If any of this connects or you want to think through something together, reach out at ivanturkovic.com/contact. What changed in how you think about the world outside technology, after years of learning inside it? --- # The Training Data Paradox: What Happens When AI Replaces the Engineers Who Trained It URL: https://www.ivanturkovic.com/2026/03/01/training-data-paradox-ai-replacing-engineers-who-trained-it/ Published: 2026-03-01 Topics: AI, Career & Leadership, Software Engineering There is a question hiding in plain sight behind every celebration of AI-generated code, every prediction that developers are obsolete, every LinkedIn post about building an app 100x faster with a prompt. It is a question that almost nobody in the current hype cycle is asking, and it may be the most important question of all. Where did the AI learn to write that code? The answer is straightforward: from decades of human engineering work. From millions of Stack Overflow answers written by developers who spent hours debugging real problems. From open source repositories maintained by people who understood not just syntax, but architecture, tradeoffs, and the messy realities of production systems. From technical documentation, conference talks, blog posts, and code reviews, all produced by human beings who earned their knowledge through years of practice. Now consider what is happening simultaneously. Stack Overflow's monthly question volume has collapsed from over 200,000 at its peak in 2014 to fewer than 4,000 by December 2025, a 78% drop from the previous year alone. Junior developer hiring has plummeted by 67% since 2022. A Harvard study tracking 62 million workers across 285,000 U.S. firms found that junior employment drops 9 to 10 percent within six quarters of companies adopting generative AI, while senior employment barely changes. Computer science graduates now face 6.1% unemployment, nearly double the national average. Meanwhile, over 74% of newly published web pages contain detectable AI-generated content. Researchers estimate that more than half of all new English-language articles online are now synthetic. A landmark paper published in Nature demonstrated that AI models trained on their own output undergo "model collapse," a degenerative process where they progressively forget rare but important patterns in the original data. This is the training data paradox: the industry is simultaneously consuming the knowledge produced by human engineers while eliminating the conditions that produced that knowledge in the first place. The Ecosystem Nobody Talks About To understand why this matters, you need to see software engineering knowledge as an ecosystem rather than a static resource. When a senior engineer writes a Stack Overflow answer explaining how to handle database transactions under concurrent load, that answer does not appear from nothing. It emerges from years of experience building systems, encountering failures, debugging production incidents, and learning from other engineers who came before them. The answer is the visible tip of an enormous iceberg of tacit knowledge. That ecosystem had a lifecycle. Junior developers asked questions. Mid-level developers answered them and learned to articulate their knowledge in the process. Senior developers refined answers, corrected misconceptions, and contributed nuanced perspectives that only come from seeing systems fail in unexpected ways. The community collectively maintained and updated this knowledge as technologies evolved. AI models consumed the output of this ecosystem at scale. Every training run ingested millions of these human knowledge artifacts. The models learned to produce code that looks correct because it is statistically modeled on code that was actually correct, written by people who understood why it was correct. But the ecosystem itself is now collapsing. Stack Overflow, which was the largest publicly accessible repository of human-verified programming knowledge, has returned to question volumes not seen since its launch year in 2008. The 2025 Stack Overflow Developer Survey revealed that while 84% of developers now use AI tools, positive sentiment toward those tools has dropped from over 70% in 2023 to just 60%. Trust remains remarkably low, with only 3.1% of developers expressing high confidence in AI output. 87% report concerns about accuracy. Developers are using AI more and trusting it less. That disconnect should concern everyone. The Model Collapse Problem The scientific evidence for what happens when AI trains on AI-generated content is now substantial and alarming. In 2024, a team of researchers from British and Canadian universities published a widely cited paper in Nature demonstrating model collapse in large language models. When successive generations of models were trained on data produced by their predecessors, the models progressively lost information about rare events and edge cases, eventually converging on a narrow, high-probability output that looked increasingly nothing like the original training distribution. The process has two stages. In early model collapse, the model loses information from the tails of the distribution, the unusual cases, the edge conditions, the rare but important patterns. This stage is particularly insidious because overall performance may appear to improve even as the model loses its ability to handle minority cases. In late model collapse, the distribution converges so dramatically that the output becomes nearly useless. A 2025 ICLR spotlight paper titled "Strong Model Collapse" went further, showing that even the smallest fraction of synthetic data in a training corpus, as little as one in a thousand samples, can still lead to model collapse. Larger training sets do not help. And critically, larger models can actually amplify the collapse rather than mitigate it. Think about what this means in practical terms. The Harvard Journal of Law and Technology has described the situation as an "Ouroboros," a snake eating its own tail. AI companies scraped the web to train their models. Those models now generate content that floods the web. Future models will be trained on a web that is increasingly composed of AI-generated content. Each generation learns from the degraded output of the last. The researchers have identified three compounding errors in this process: statistical approximation errors from finite sampling, functional expressivity errors from the limited capacity of models, and functional approximation errors from the biases in learning procedures. These errors accumulate across generations. They do not self-correct. The Junior Developer Pipeline Crisis The training data paradox does not only operate at the level of web content and model training. It operates at the level of human knowledge development itself. Junior developer hiring has collapsed across the industry. Entry-level developer opportunities have dropped approximately 67% since 2022. In the UK, tech graduate roles fell 46% in 2024, with projections for a further 53% drop by 2026. A Stanford Digital Economy Study found that employment for software developers aged 22 to 25 declined nearly 20% from its peak in late 2022 by July 2025. In the United States, overall programmer employment fell 27.5% between 2023 and 2025 according to the Bureau of Labor Statistics. The logic behind these cuts seems rational from a quarterly earnings perspective. If an AI coding assistant can handle boilerplate work, why hire juniors to do it? If a senior developer augmented by AI can be 20% more productive, why not let them absorb the work that would have gone to two juniors? But this logic contains a fatal assumption: that the supply of senior engineers is somehow independent of the supply of juniors. It is not. Every senior engineer was once a junior. Every architect once struggled with their first database schema. Every tech lead once submitted a pull request that was mostly wrong. The junior developer pipeline is not overhead. It is the mechanism through which the industry reproduces its own expertise. When companies stop hiring juniors, they are not just cutting costs. They are cutting off the pipeline that produces the senior engineers they will desperately need in five to ten years. A 67% hiring cliff in 2024 to 2026 means 67% fewer potential tech leads in 2031 to 2036. The industry is eating its seed corn. AWS CEO Matt Garman called the idea of replacing junior developers with AI "one of the dumbest things I've ever heard." Microsoft's Mark Russinovich and Scott Hanselman have publicly argued that companies must hire junior developers and teach them to fix the mistakes made by AI. But the industry trend moves in the opposite direction. The Double Helix of Degradation What makes the training data paradox particularly dangerous is that these two degradation loops reinforce each other. The first loop is technical: AI models trained on increasingly synthetic data produce increasingly degraded output. The web fills with AI-generated content. Future models train on that content. Quality degrades further. The second loop is human: fewer junior developers enter the profession. Fewer humans produce the kind of hard-won, experience-driven knowledge that made the training data valuable in the first place. The knowledge ecosystem that produced the original high-quality training data withers. These two loops feed into each other. As AI output quality degrades due to model collapse, the need for skilled human engineers who can catch errors and provide genuine expertise increases. But those humans are not being developed because the industry has decided AI makes them unnecessary. And the fewer humans contributing genuine knowledge, the faster the synthetic content ratio climbs, accelerating model collapse. It is a vicious cycle with a time delay built in. The consequences of decisions being made today will not become fully visible for five to ten years. By then, the damage to the knowledge ecosystem may be difficult to reverse. What the Numbers Actually Say Let me lay out the data points that together paint this picture. On the synthetic content side: an Ahrefs analysis of 900,000 newly published web pages in April 2025 found that 74.2% contained detectable AI-generated content. A Graphite report analyzing 65,000 English-language articles found that AI-generated content has reached a roughly 50/50 split with human-written content. AI-written pages appearing in the top 20 Google search results climbed from 11.1% to 19.6% between May 2024 and July 2025. NewsGuard tracked AI "news" sites growing from 49 to 1,271 between May 2023 and May 2025. On the knowledge ecosystem side: Stack Overflow monthly questions fell from 200,000 to under 4,000. The 2025 Developer Survey shows 84% of developers using AI tools but only 3.1% expressing high trust in the output. 76% of developers say they will not use AI for deployment and monitoring. 69% say they will not use it for project planning. The tools are being used for the easy parts. The hard parts still require human judgment that fewer humans are being trained to develop. On the hiring side: a Harvard study of 285,000 firms shows 9 to 10% junior employment decline per AI adoption wave. U.S. programmer employment fell 27.5% between 2023 and 2025. CS graduate unemployment sits at 6.1%. Computer engineering graduate unemployment is at 7.5%, higher than fine arts graduates. 54% of engineering leaders surveyed by LeadDev plan to hire fewer juniors due to AI. On the productivity illusion side: businesses self-report a 24.7% productivity increase from AI adoption. But actual measured results from studies of 39,000 developers show only a 2.1% overall productivity increase and a 3.4% code quality improvement. Software delivery performance actually declined 7.2% in some studies. The gap between perceived and actual productivity gains is roughly 12x. These numbers tell a consistent story. The industry is making dramatic structural changes based on dramatically overestimated productivity gains, while the actual foundations of software quality are being eroded. The Photocopier Analogy The Harvard Journal of Law and Technology used an analogy that I think captures the situation perfectly: model collapse is like repeatedly photocopying a picture. Each copy is slightly degraded from the last. After enough generations, the image becomes unrecognizable. But the analogy needs to be extended. Imagine that the original photographs were taken by skilled photographers who spent decades learning their craft. Now imagine that, because the photocopier exists, you decide to stop training new photographers. After all, the machine can produce images. Why invest in expensive human expertise? For a while, the photocopies look fine. Close enough, at least. But each generation loses a little more detail. And because no new photographers are being trained, there are no new original photographs being taken to refresh the source material. The machine copies copies of copies, each time losing something. And the humans who could have created genuinely new, high-quality originals were never given the chance to learn. This is where the software industry is heading. Not with a dramatic crash, but with a slow, almost imperceptible degradation. Code that works but handles fewer edge cases. Systems that function but fail in increasingly surprising ways. Documentation that reads well but contains subtle inaccuracies that compound over time. An industry that gradually loses the deep expertise that made its products reliable in the first place. The Pre-2022 Data Advantage There is an irony here that deserves attention. AI companies are increasingly aware that data collected before the generative AI explosion of 2022 is qualitatively different from what is available now. Pre-2022 web data was predominantly human-authored. Post-2022 data is increasingly synthetic. The contamination is difficult to detect and nearly impossible to fully remove. This creates what researchers have called a "data moat" for incumbent AI companies. Organizations that scraped and stored large datasets before 2022 possess something that future competitors cannot easily replicate: a corpus of genuinely human-generated knowledge. New entrants to the AI market face an increasingly polluted web that makes high-quality training data harder to find. The implications extend beyond corporate competition. If the highest-quality AI models depend on pre-2022 human knowledge, and the industry is simultaneously eliminating the conditions that produced that knowledge, then we are living off a finite inheritance. We are spending the intellectual capital accumulated by generations of engineers while failing to invest in creating more. Some AI companies are attempting to address this through data licensing deals. Stack Overflow, despite its traffic collapse, achieved 17% revenue growth by licensing its data to AI providers. Reddit has similarly monetized its archive. But these deals do not solve the fundamental problem. The data being licensed is historical. It represents what humans knew and discussed in the past. Without ongoing human knowledge creation, these archives become increasingly stale. What Actually Sustains Software Quality The training data paradox reveals something important about the nature of software engineering knowledge that the "developers are dead" crowd consistently misses. Software quality does not come from code generation. It comes from a complex ecosystem of human activities: understanding business requirements deeply enough to know which edge cases matter, making architectural decisions that balance competing concerns, debugging failures that only appear under specific production conditions, reviewing code with an understanding of how systems evolve over time, mentoring junior engineers who will carry institutional knowledge forward. None of these activities produce the kind of clean, structured data that AI models train on effectively. They happen in conversations, in whiteboard sessions, in the accumulated judgment of experienced engineers who have seen enough systems fail to recognize warning signs early. This knowledge is largely invisible to AI training pipelines. The 2025 Stack Overflow Developer Survey confirmed this. The most common activity when developers visit Stack Overflow is reading comments, not reading answers. The comments are where the nuance lives. Where experienced developers say "this works, but be careful in multi-threaded environments" or "this solution is correct for PostgreSQL but will fail silently on MySQL." That contextual, conditional, experience-driven knowledge is exactly what AI models struggle to capture and exactly what model collapse erodes first. This is the tail of the distribution that the Nature paper warned about. Edge cases, rare conditions, platform-specific quirks, the kind of knowledge that only comes from years of production experience. Model collapse erases these tails first. They are the minority data. They are also the data that prevents systems from failing in production. The Uncomfortable Economics The economic incentives driving the training data paradox are powerful and, in the short term, entirely rational. For individual companies, cutting junior headcount and relying on AI-augmented seniors saves money this quarter. Licensing AI tools is cheaper than hiring. The productivity reports, even if inflated, provide cover for cost-cutting decisions that executives were already inclined to make. For AI companies, the incentive is to acquire and license as much human-generated data as possible while the getting is good. The value of their models depends on training data quality. They know this. But they also know that the market rewards capability today, not sustainability tomorrow. For individual developers, the incentive to contribute knowledge to public platforms is declining. Why spend an hour writing a detailed Stack Overflow answer when the platform's traffic has collapsed, the answer will be scraped into an AI training set, and the AI will receive the credit? The rational response is to stop contributing. And that is exactly what is happening. This is a classic tragedy of the commons. The shared resource, the global pool of human software engineering knowledge, is being depleted because no individual actor bears the full cost of its loss. Everyone benefits from the knowledge. No one has sufficient incentive to sustain it. What Can Be Done I do not have a neat solution to the training data paradox. But I can identify what needs to happen for the industry to avoid the worst outcomes. Companies need to maintain junior developer pipelines. Not out of charity, but out of long-term self-interest. The senior engineers of 2035 are the juniors who should be getting hired today. Companies that cut this pipeline entirely are making a bet that AI will replace human judgment within a decade. If that bet is wrong, and six decades of technology history suggest it will be, they will face a talent crisis they cannot quickly fix. The industry needs to value and protect high-quality human-generated knowledge. This means creating economic models that compensate knowledge creators rather than simply scraping their output. Stack Overflow's data licensing deals are a step in this direction, but the compensation needs to flow to the individual contributors, not just the platform. AI companies need to invest seriously in data provenance and synthetic data detection. Training on contaminated data is not just an abstract research concern. It is a product quality issue. Models that progressively degrade because of synthetic data contamination will lose market position to competitors that maintain data quality. The ICLR research showing that even one-in-a-thousand synthetic samples can trigger collapse should be taken seriously. Individual engineers need to continue investing in deep understanding. Not because it is noble, but because it is practical. As AI-generated code quality degrades over time, the ability to evaluate, debug, and correct that code becomes more valuable, not less. The developers who understand systems, tradeoffs, and architecture will be the ones who keep things running when the AI-generated layers start to show their seams. And all of us, as an industry, need to be honest about what is happening. The narrative that AI will simply replace developers is not just wrong in the historical sense I explored in my previous piece, "The Eternal Promise." It is actively harmful because it accelerates the destruction of the very knowledge ecosystem that AI depends on. The Paradox Restated The training data paradox is this: AI learned from the best of what human engineers produced over decades. The industry is now using AI as justification to stop producing the kind of human engineering knowledge that made AI valuable in the first place. If this continues unchecked, the models will degrade, the talent pipeline will dry up, and the industry will face a compounding crisis of declining AI quality and declining human expertise simultaneously. Software is crystallized thought. AI models are crystallized from the crystallized thoughts of millions of developers. If we stop training the developers who produce those thoughts, we are melting the source material. The question is not whether AI can write code. It can. The question is whether the code it writes will get better or worse over time. And the answer to that question depends entirely on whether we maintain the human knowledge ecosystem that AI was built on. History does not repeat itself exactly. But it does have patterns. And the pattern here, the one where an industry consumes a resource faster than it can be replenished, is one of the oldest stories in human civilization. The engineers who recognize this paradox and act accordingly will be the ones who remain indispensable. Not because they resist AI, but because they understand what AI cannot sustain on its own. Final Thoughts If this analysis resonated, you might also want to read my earlier piece, The Eternal Promise: A History of Attempts to Eliminate Programmers, which recently sparked a 160+ comment discussion on Hacker News. Together, these two posts explore both the historical pattern and the emerging systemic risk. I write regularly about the intersection of software engineering, AI, and the realities of building systems in production. You can find me on Twitter (X) or LinkedIn for shorter insights and discussions, or follow this blog for longer explorations. I would genuinely like to hear your perspective on this. Are you seeing the knowledge ecosystem decline in your own work? Have you noticed AI output quality changing over time? Are companies in your network cutting junior hiring and, if so, are they thinking about the long-term consequences? Leave a comment below, connect with me on social media, or reach out through the contact page. The conversations that follow these posts are often as valuable as the posts themselves. Until next time, keep building wisely. --- # AI Made Writing Code Easier. It Made Being an Engineer Harder. URL: https://www.ivanturkovic.com/2026/02/25/ai-made-writing-code-easier-engineering-harder/ Published: 2026-02-25 Topics: AI, Career & Leadership, Software Engineering Yes, writing code is easier than ever. AI assistants autocomplete your functions. Agents scaffold entire features. You can describe what you want in plain English and watch working code appear in seconds. The barrier to producing code has never been lower. And yet, the day-to-day life of software engineers has gotten more complex, more demanding, and more exhausting than it was two years ago. This is not a contradiction. It is the reality of what happens when an industry adopts a powerful new tool without pausing to consider the second-order effects on the people using it. If you are a software engineer reading this and feeling like your job quietly became harder while everyone around you celebrates how easy everything is now, you are not imagining things. The job changed. The expectations changed. And nobody sent a memo. The Baseline Moved and Nobody Told You There is a phenomenon happening right now that most engineers feel but struggle to articulate. The expected output of a software engineer in 2026 is dramatically higher than it was in 2023. Not because anyone held a meeting and announced new targets. Not because your manager sat you down and explained the new rules. The baseline just moved. It moved because AI tools made certain tasks faster. And when tasks become faster, the assumption follows immediately: you should be doing more. Not in the future. Now. A February 2026 study published in Harvard Business Review tracked 200 employees at a U.S. tech company over eight months. The researchers found something that will sound familiar to anyone living through this shift. Workers did not use AI to finish earlier and go home. They used it to do more. They took on broader tasks, worked at a faster pace, and extended their hours, often without anyone asking them to. The researchers described a self-reinforcing cycle: AI accelerated certain tasks, which raised expectations for speed. Higher speed made workers more reliant on AI. Increased reliance widened the scope of what workers attempted. And a wider scope further expanded the quantity and density of work. The numbers tell the rest of the story. Eighty-three percent of workers in the study said AI increased their workload. Burnout was reported by 62 percent of associates and 61 percent of entry-level workers. Among C-suite leaders? Just 38 percent. The people doing the actual work are carrying the intensity. The people setting the expectations are not feeling it the same way. This gap matters enormously. If leadership believes AI is making everything easier while engineers are drowning in a new kind of complexity, the result is a slow erosion of trust, morale, and eventually talent. A separate survey of over 600 engineering professionals found that nearly two-thirds of engineers experience burnout despite their organizations using AI in development. Forty-three percent said leadership was out of touch with team challenges. Over a third reported that productivity had actually decreased over the past year, even as their companies invested more in AI tooling. The baseline moved. The expectations rose. And for many engineers, no one acknowledged that the job they signed up for had fundamentally changed. The Identity Crisis Nobody Talks About Here is something that gets lost in all the excitement about AI productivity: most software engineers became engineers because they love writing code. Not managing code. Not reviewing code. Not supervising systems that produce code. Writing it. The act of thinking through a problem, designing a solution, and expressing it precisely in a language that makes a machine do exactly what you intended. That is what drew most of us to this profession. It is a creative act, a form of craftsmanship, and for many engineers, the most satisfying part of their day. Now they are being told to stop. Not explicitly, of course. Nobody walks into a standup and says "stop writing code." But the message is there, subtle and persistent. Use AI to write it faster. Let the agent handle the implementation. Focus on higher-level tasks. Your value is not in the code you write anymore, it is in how well you direct the systems that write it for you. For early adopters, this feels exciting. It feels like evolution. For a significant portion of working engineers, it feels like being told that the thing they spent years mastering, the skill that defines their professional identity, is suddenly less important. One engineer captured this shift perfectly in a widely shared essay, describing how AI transformed the engineering role from builder to reviewer. Every day felt like being a judge on an assembly line that never stops. You just keep stamping those pull requests. The production volume went up. The sense of craftsmanship went down. This is not a minor adjustment. It is a fundamental shift in professional identity. Engineers who built their careers around deep technical skill are being asked to redefine what they do and who they are, essentially overnight, without any transition period, training, or acknowledgment that something significant was lost in the process. Having led engineering teams for over two decades, I have seen technology shifts before. New frameworks, new languages, new methodologies. Engineers adapt. They always have. But this is different because it is not asking engineers to learn a new way of doing what they do. It is asking them to stop doing the thing that made them engineers in the first place and become something else entirely. That is not an upgrade. That is a career identity crisis. And pretending it is not happening does not make it go away. The Expanding Role: When Everything Becomes Your Problem While engineers are being asked to write less code, they are simultaneously being asked to do more of everything else. More product thinking. More architectural decision-making. More code review. More context switching. More planning. More testing oversight. More deployment awareness. More risk assessment. The scope of what it means to be a "software engineer" expanded dramatically in the last two years, and it happened without a pause to catch up. This is partly a direct consequence of AI acceleration. When code gets produced faster, the bottleneck shifts. It moves from implementation to everything surrounding implementation: requirements clarity, architecture decisions, integration testing, deployment strategy, monitoring, and maintenance. These were always part of the engineering lifecycle, but they were distributed across roles. Product managers handled requirements. QA handled testing. DevOps handled deployment. Senior architects handled system design. Now, with AI collapsing the implementation phase, organizations are quietly redistributing those responsibilities to the engineers themselves. The Harvard Business Review study documented this exact pattern. Product managers began writing code. Engineers took on product work. Researchers started doing engineering tasks. Roles that once had clear boundaries blurred as workers used AI to handle jobs that previously sat outside their remit. The industry is openly talking about this as a positive development. Engineers should be "T-shaped" or "full-stack" in a broader sense. Nearly 45 percent of engineering roles now expect proficiency across multiple domains. AI tools augment generalists more effectively, making it easier for one person to handle multiple components of a system. On paper, this sounds empowering. In practice, it means that a mid-level backend engineer is now expected to understand product strategy, review AI-generated frontend code they did not write, think about deployment infrastructure, consider security implications of code they cannot fully trace, and maintain a big-picture architectural awareness that used to be someone else's job. That is not empowerment. That is scope creep without a corresponding increase in compensation, authority, or time. From my experience building and scaling teams in fintech and high-traffic platforms, I can tell you that role expansion without clear boundaries always leads to the same outcome: people try to do everything, nothing gets done with the depth it requires, and burnout follows. The engineers who survive are the ones who learn to say no, to prioritize ruthlessly, and to push back when the scope of their role quietly doubles without anyone acknowledging it. The Supervision Paradox There is an irony at the center of the AI-assisted engineering workflow that nobody wants to talk about: reviewing AI-generated code is often harder than writing the code yourself. When you write code, you carry the context of every decision in your head. You know why you chose this data structure, why you handled this edge case, why you structured the module this way. The code is an expression of your thinking, and reviewing it later is straightforward because the reasoning is already stored in your memory. When AI writes code, you inherit the output without the reasoning. You see the code, but you do not see the decisions. You do not know what tradeoffs were made, what assumptions were baked in, what edge cases were considered or ignored. You are reviewing someone else's work, except that someone is not a colleague you can ask questions. It is a statistical model that produces plausible-looking code without any understanding of your system's specific constraints. A survey by Harness found that 67 percent of developers reported spending more time debugging AI-generated code, and 68 percent spent more time reviewing it than they did with human-written code. This is not a failure of the tools. It is a structural property of the workflow. Code review without shared context is inherently more demanding than reviewing code you participated in creating. Yet the expectation from management is that AI should be making everything faster. So engineers find themselves in a bind: they are producing more code than ever, but the quality assurance burden has increased, the context-per-line-of-code has decreased, and the cognitive load of maintaining a system they only partially built is growing with every sprint. This is the supervision paradox. The faster AI generates code, the more human attention is required to ensure that code actually works in the context of a real system with real users and real business constraints. The production bottleneck did not disappear. It moved from writing to understanding, and understanding is harder to speed up. The Acceleration Trap What makes all of this especially difficult is the self-reinforcing nature of the cycle. AI makes certain tasks faster. Faster tasks create the perception of more available capacity. More perceived capacity leads to more work being assigned. More work leads to more AI reliance. More AI reliance leads to more code that needs review, more context that needs to be maintained, more systems that need to be understood, and more cognitive load on engineers who are already stretched thin. The Harvard Business Review researchers described this as "workload creep." Workers did not consciously decide to work harder. The expansion happened naturally, almost invisibly. Each individual step felt reasonable. In aggregate, it produced an unsustainable pace. Before AI, there was a natural ceiling on how much you could produce in a day. That ceiling was set by thinking speed, typing speed, and the time it takes to look things up. It was frustrating sometimes, but it was also a governor. A natural speed limit that prevented you from outrunning your own ability to maintain quality. AI removed the governor. Now the only limit is your cognitive endurance. And most people do not know their cognitive limits until they have already blown past them. This is where many engineers find themselves right now. Shipping more code than any quarter in their career. Feeling more drained than any quarter in their career. The two facts are not unrelated. The trap is that it looks like productivity from the outside. Metrics go up. Velocity charts look great. More features shipped. More pull requests merged. But underneath the numbers, quality is quietly eroding, technical debt is accumulating faster than it can be addressed, and the people doing the work are running on fumes. What Junior Engineers Are Facing If the picture is difficult for experienced engineers, it is even harder for those starting their careers. Junior engineers have traditionally learned by doing the simpler, more task-oriented work. Fixing small bugs. Writing straightforward features. Implementing well-defined tickets. This hands-on work built the foundational understanding that eventually allowed them to take on more complex challenges. AI is rapidly consuming that training ground. If an agent can handle the routine API hookup, the boilerplate module, the straightforward CRUD endpoint, what is left for a junior engineer to learn from? The expectation is shifting toward needing to contribute at a higher level almost from day one, without the gradual ramp-up that previous generations of engineers relied on. Entry-level hiring at the 15 largest tech firms fell 25 percent from 2023 to 2024. The HackerRank 2025 Developer Skills Report confirmed that expectations are rising faster than productivity gains, and that early-career hiring remains sluggish compared to senior-level roles. Companies are prioritizing experienced talent, but the pipeline that produces experienced talent is being quietly dismantled. This is a problem that extends beyond individual career concerns. If junior engineers do not get the opportunity to build foundational skills through hands-on work, the industry will eventually face a shortage of senior engineers who truly understand the systems they oversee. You cannot supervise what you never learned to build. As I have written before, code is for humans to read. If the next generation of engineers never develops the fluency to read, understand, and reason about code at a deep level, no amount of AI tooling will compensate for that gap. What Good Leadership Looks Like Right Now If you lead engineering teams, the most important thing you can do right now is acknowledge that this transition is genuinely difficult. Not theoretically. Not abstractly. For the actual people on your team. The career they signed up for changed fast. The skills they were hired for are being repositioned. The expectations they are working under shifted without a clear announcement. Acknowledging this reality is not a sign of weakness. It is a prerequisite for maintaining a team that trusts you. Start with empathy, but do not stop there. Give your team real training. Not a lunch-and-learn about prompt engineering. Real investment in the skills that the new engineering landscape actually requires: system design, architectural thinking, product reasoning, security awareness, and the ability to critically evaluate code they did not write. These are not trivial skills. They take time to develop, and your team needs structured support to build them. Give them space to experiment without the pressure of immediate productivity gains. The engineers who will thrive in this environment are the ones who have room to figure out how AI fits into their workflow without being penalized for the learning curve. Every experienced technologist I know who has successfully integrated AI tools went through an adjustment period where they were less productive before they became more productive. That adjustment period is normal, and it needs to be protected. Set explicit boundaries around role scope. If you are asking engineers to take on product thinking, planning, and risk assessment in addition to their technical work, name it. Define it. Compensate for it. Do not let it happen silently and then wonder why your team is burned out. Rethink your metrics. If your engineering success metrics are still centered on velocity, tickets closed, and lines of code, you are measuring the wrong things in an AI-assisted world. System stability, code quality, decision quality, customer outcomes, and team health are better indicators of whether your engineering organization is actually producing value or just producing volume. Protect the junior pipeline. If you have stopped hiring junior engineers because AI can handle entry-level tasks, you are solving a short-term efficiency problem by creating a long-term talent crisis. The senior engineers you rely on today were junior engineers who learned by doing the work that AI is now consuming. That path still matters. And finally, keep challenging your team. I have never met a good engineer who did not love a good challenge. The engineers on your team are not fragile. They are capable, intelligent people who signed up for hard problems. They can handle this transition. Just make sure they are set up to meet it. What Engineers Can Do for Themselves If you are an engineer navigating this shift, here is what I would tell you based on two decades of watching technology cycles reshape this profession. First, do not abandon your fundamentals. The pressure to become an "AI-first" engineer is real, but the engineers who will be most valuable in five years are the ones who deeply understand the systems they work on. AI is a tool. Understanding architecture, debugging complex systems, reasoning about performance and security: these skills are not becoming less important. They are becoming more important because someone needs to be the adult in the room when AI-generated code breaks in production at 2 AM. Second, learn to set boundaries with the acceleration trap. Just because you can produce more does not mean you should. Sustainable pace matters. The engineers who burn out trying to match the theoretical maximum output AI makes possible are not the ones who build lasting careers. The ones who learn to work with AI deliberately, choosing when to use it and when to think independently, are the ones who will still be thriving in this profession a decade from now. Third, embrace the parts of the expanded role that genuinely interest you. If the engineering role now includes more product thinking, more architectural decision-making, more cross-functional communication, treat that as an opportunity rather than an imposition. These are skills that senior engineers and technical leaders need. You are being given access to a broader set of capabilities earlier in your career than any previous generation of engineers. That is not a burden. It is a head start. Fourth, talk about what you are experiencing. The isolation of feeling like you are the only one struggling with this transition is one of the most damaging aspects of the current moment. You are not the only one. The data confirms it. Two-thirds of engineers report burnout. The expectation gap between leadership and engineering teams is well documented. Talking openly about these challenges, with your team, with your manager, with your broader network, is not complaining. It is professional honesty. And fifth, remember that this profession has survived every prediction of its demise. COBOL was supposed to eliminate programmers. Expert systems were supposed to replace them. Fourth-generation languages, CASE tools, visual programming, no-code platforms, outsourcing. Every decade brings a new technology that promises to make software engineers obsolete, and every decade the demand for skilled engineers grows. AI will not be different. The tools change. The fundamentals endure. The Paradox We Need to Name AI made writing code easier and made being an engineer harder. Both of these things are true at the same time, and pretending that only the first one matters is how organizations lose their best people. The engineers who are struggling right now are not struggling because they are bad at their jobs. They are struggling because their jobs changed underneath them while the industry celebrated the part that got easier and ignored the parts that got harder. Expectations rose without announcement. Roles expanded without boundaries. Output demands increased without corresponding increases in support, training, or acknowledgment. And the engineers who raised concerns were told, implicitly or explicitly, that they just needed to adapt faster. That is not how you build a sustainable engineering culture. That is how you build a burnout machine. The industry needs to name this paradox honestly. AI is an incredible tool. It is also placing enormous new demands on the people using it. Both things can be true. Both things need to be addressed. The organizations that get this right, that invest in their people alongside their tools, that acknowledge the human cost of rapid technological change while still pushing forward, those are the organizations that will attract and retain the best engineering talent in the years ahead. The ones that do not will discover something that every technology cycle eventually teaches: tools do not build products. People do. And people have limits that no amount of AI can automate away. If this resonated with you, I would love to hear your perspective. What has changed most about your engineering role in the last year? Drop me a message or connect with me on Twitter (X) or LinkedIn. I write regularly about the intersection of AI, software engineering, and leadership at ivanturkovic.com. Follow along if you want honest, experience-driven perspectives on how technology is actually changing this profession. --- # The First 1,000 Lines Determine the Next 100,000 in AI Coding URL: https://www.ivanturkovic.com/2026/02/24/first-1000-lines-determine-next-100000-ai-coding/ Published: 2026-02-24 Topics: AI, AI-Driven Development, Software Engineering I learned this the hard way while working with Claude Code. AI looks at your existing code and copies the patterns it finds. If you start with clean code, the rest stays clean. If you start messy, the problems pile up faster than any human team could create them. And unlike a junior developer who might occasionally question a strange pattern, AI agents replicate what they see with perfect consistency and zero judgment. This is the lesson that most conversations about AI-assisted development completely miss. Everyone is talking about which model is smarter, which agent writes faster code, which IDE integration has the best autocomplete. Almost nobody is talking about the thing that actually determines whether AI makes your codebase better or destroys it: the quality of the code that already exists when the AI starts working. The first 1,000 lines of code determine the next 100,000. And in the age of AI coding agents, that statement has never been more literally true. AI Is an In-Context Learner, Not a Standards Enforcer To understand why foundations matter so much in AI-assisted development, you need to understand a fundamental truth about how these tools work. AI coding agents are in-context learners. They do not arrive with an opinionated stance on how your code should be structured. They arrive, scan your codebase, identify the patterns you have established, and then replicate those patterns as faithfully as they can. This is both their greatest strength and their most dangerous quality. When Claude Code starts a session, it reads your project structure. It examines your CLAUDE.md file if you have one. It looks at your directory layout, your naming conventions, your error handling patterns, your test structure, your API design. Then it generates new code that mirrors what it found. If your services follow a clean pattern with proper separation of concerns, the AI produces services that follow the same pattern. If your error handling is consistent and thorough, the AI writes consistent and thorough error handling. If your tests are well-organized with clear assertions and meaningful descriptions, the AI writes tests that match. But here is the part nobody wants to hear: the reverse is equally true. If your services are a tangled mess of mixed responsibilities, the AI produces more tangled services. If your error handling is inconsistent or missing in places, the AI writes code with the same gaps. If your tests are superficial or poorly structured, every new test the AI generates inherits those same weaknesses. The AI is not judging your code. It is learning from it. And it learns from bad examples just as efficiently as it learns from good ones. What I Saw in My Own Projects I have seen this play out across multiple projects over the past year, and the contrast is striking. In projects where I invested time upfront in building a clean foundation, the AI became remarkably productive. I focused on several things before writing a single feature: a deliberate directory structure that made the purpose of each module obvious, shared utilities for common operations like error handling and validation, strict naming conventions that remained consistent across the entire codebase, and explicit rules for the AI agents including coding style guidelines, API design patterns, and design system constraints. The result was transformative. When I use Claude Code to add a new feature in those projects, it picks up the patterns immediately. It puts files in the right directories. It uses the existing utility functions instead of reinventing them. It follows the naming conventions without being reminded. Most of the time, I can describe a feature in plain language and get a working implementation on the first attempt. Not because the AI is magically brilliant, but because the foundation gives it everything it needs to make the right decisions. Now compare that with the projects where I rushed the basics. Where I told myself I would "clean it up later" because I needed to ship something quickly. In those projects, working with AI is a constant battle. The agent generates code that works but does not fit. It creates duplicate utilities because it cannot find the existing ones buried in inconsistent locations. It invents new naming patterns because the existing ones contradict each other. Every feature requires multiple rounds of correction, and each correction introduces new inconsistencies that the AI then learns from and propagates further. I am still dealing with the fallout from those rushed starts. And the cruel irony is that using AI in those codebases actually made the problems worse, not better. The mess compounds faster because the AI can produce messy code at a pace no human team could match. The Data Confirms What Experience Shows My experience is not unique, and the industry data backs it up with alarming clarity. The SonarSource State of Code Developer Survey from 2025, which surveyed over 1,100 developers globally, found that 72% of developers now use AI coding tools regularly, with 42% of their committed code being AI-generated or AI-assisted. But the critical insight was not about adoption rates. It was about amplification. The report concluded explicitly that AI does not fix a broken system. It simply amplifies whatever already exists. Teams with well-architected systems and robust testing saw AI amplify their efficiency. Teams with chaotic systems saw AI amplify the chaos, producing what the researchers described as "looks correct but isn't" code, faster than ever before. GitClear's analysis of over 211 million changed lines of code between 2020 and 2024 paints an equally concerning picture. They found a fourfold increase in duplicated code blocks and a 60% decline in refactored code since AI coding tools became mainstream. Developers are generating more code but reusing less of it, and the code that gets generated carries patterns from whatever context the AI was working within. When that context is clean and well-organized, the output is usable. When it is not, the output creates what researchers are now calling "comprehension debt," where the codebase becomes so inconsistent that no one, human or AI, can reliably understand what it is doing. Ox Security's 2025 analysis of 300 open-source repositories found that AI-generated code is functionally capable but systematically lacking in architectural judgment. Ten recurring anti-patterns appeared in 80 to 100 percent of the AI-generated codebases they examined, including incomplete error handling, weak concurrency management, and inconsistent architecture. The researchers described AI-generated code as behaving like "an army of talented juniors without oversight." That description resonates perfectly with what I have experienced. The AI does not lack talent. It lacks the architectural context that only a well-built foundation can provide. Perhaps the most sobering finding comes from a 2025 METR study that revealed a 39 to 44 percent perception gap in AI-assisted development. Developers believed they were working 20% faster with AI tools, but measurements showed they were actually 19% slower in real-world codebases. The explanation is straightforward: the time saved on initial generation was consumed by debugging, correcting, and refactoring the output. And the worse the foundation, the wider that gap becomes. What a Good Foundation Actually Looks Like If you are starting a greenfield project today and planning to use AI coding agents, the single most important thing you can do is invest in your foundation before writing a single feature. This is not about premature optimization or over-engineering. It is about giving the AI the patterns it needs to work effectively on your behalf. A good foundation for AI-assisted development starts with an intentional directory structure. The layout of your project is one of the first things an AI agent reads when it begins working. If your structure is logical and self-documenting, with clear separation between domains, shared utilities, configuration, and tests, the AI understands immediately where things belong. If your structure is flat, ambiguous, or inconsistent, the AI guesses. And its guesses become the new pattern. Next comes shared utilities and base patterns. Before you write your first endpoint or component, write the foundational code that everything else will depend on. Error handling utilities, validation helpers, logging configurations, database connection patterns, authentication middleware. These are the building blocks that the AI will reference for every subsequent piece of code it generates. If they are well-written, every feature the AI creates inherits that quality. If they are missing, the AI invents its own version every time, and each version will be slightly different. Naming conventions matter more than most developers realize, especially in AI-assisted workflows. The AI learns your naming patterns from the existing code and replicates them. If your variable names, function names, file names, and database column names follow a consistent convention, the AI maintains that consistency automatically. If your naming is a mixture of camelCase here, snake_case there, abbreviated in some places and verbose in others, you get a codebase that feels like it was written by a dozen different people, which in a sense, it was. Then there are the explicit rules for AI agents. Tools like Claude Code support CLAUDE.md files, which serve as persistent instructions that the AI reads at the start of every session. This is where you document your coding style preferences, your API design patterns, your testing conventions, your architectural decisions, and any project-specific constraints. Think of it as a style guide that never gets forgotten. In my experience, a well-written CLAUDE.md file is worth more than a hundred individual prompts. It ensures consistency across every interaction, which means consistency across every line of code the AI produces. Finally, write at least one complete feature the right way before letting the AI take over. One fully implemented endpoint with proper error handling, validation, tests, and documentation gives the AI a concrete example to follow. This is more powerful than any written instruction because the AI can see exactly how abstract principles translate into actual code in your specific project context. The Greenfield Advantage You Cannot Afford to Waste Starting a greenfield project is a rare opportunity. Most of your career as a developer is spent working within existing codebases, navigating decisions made years ago by people who may no longer be around. A greenfield project gives you a blank slate. And in the AI era, that blank slate is more valuable than it has ever been. Here is why. In a legacy codebase, the AI learns from whatever accumulated history exists, good patterns and bad ones mixed together with years of compromises and quick fixes. You can mitigate this with careful instructions and selective context, but you are always fighting against the gravity of the existing code. In a greenfield project, every pattern the AI encounters is one you deliberately placed there. You have complete control over what the AI learns, which means you have complete control over the quality of everything it produces going forward. This is the greenfield advantage, and it disappears the moment you start cutting corners. Once you introduce inconsistency into a new codebase, the AI picks it up and multiplies it. Once you write a quick-and-dirty utility "just for now," that utility becomes the template for every similar utility the AI generates afterward. The window for establishing clean patterns is narrow, and in AI-assisted development, the cost of missing it is exponentially higher than it was in traditional development. I have seen teams treat their greenfield projects the same way they always did: rush to get something working, plan to refactor later. In the pre-AI world, that approach was expensive but manageable. Technical debt accumulated slowly, at the pace of human developers. In the AI world, technical debt compounds at the speed of code generation, which is orders of magnitude faster. What used to take a year to become unmanageable can now happen in weeks. Why This Matters More for Teams Than for Individuals Everything I have described so far is amplified when you move from a solo developer to a team environment. When a single developer uses AI coding tools on a well-structured personal project, they have the context to catch when the AI drifts from their intended patterns. They know what the code should look like because they wrote the foundation themselves. But on a team, each developer brings the AI into their own workflow with their own interpretation of the project's patterns. If those patterns are not explicitly defined and embedded in the codebase and its documentation, every developer's AI generates slightly different code. Not wrong code necessarily, but inconsistent code. And inconsistency in a codebase is the seed of every future maintenance nightmare. This is why teams adopting AI coding tools need stricter early code reviews, not looser ones. The old wisdom of "move fast and break things" was already questionable, but in the AI era, it becomes genuinely dangerous. When a developer rushes a pattern into the codebase and five other developers' AI agents pick it up and replicate it across dozens of files before anyone notices, you do not have one thing to fix. You have a systemic pattern to untangle. The "move fast and fix later" mentality does not just remain expensive. It becomes more expensive than ever. Fixing a pattern that has been replicated by AI across an entire codebase is fundamentally different from fixing a pattern that one developer used in a few files. The scale of propagation is different. The speed of propagation is different. And the confidence required to know you have caught every instance is different. This is also why senior engineers matter more in the AI era, not less. They are the ones who set the patterns that everything else inherits. A senior engineer who invests two days building a clean, well-documented foundation before any features are written is not slowing the team down. They are defining the quality ceiling for every line of code the AI will ever produce in that project. That leverage is enormous, and it is the kind of leverage that only comes from experience, not from faster typing or better prompts. The Illusion of Speed Shipping quickly feels good in the moment. There is a genuine dopamine hit when you ask an AI agent to build something and it produces working code in seconds. It feels like you are moving at a pace that was never possible before. And in a narrow sense, you are. The code appears faster than any human could write it. But appearing fast and being fast are not the same thing. Speed in software development has never been measured by how quickly you can produce the first version of something. It is measured by how quickly you can produce the tenth version, the fiftieth version, the version that has to integrate with three other systems and handle edge cases nobody thought of at the start. And that is where the foundation makes all the difference. A clean codebase with consistent patterns lets the AI operate at full speed indefinitely. Each new feature fits cleanly into the existing structure. Each modification builds on solid ground. The speed you get in week one is the same speed you get in month six and year two. A messy codebase gives you explosive speed in the first week and then a slow, grinding deceleration as the inconsistencies pile up. By month three, you are spending more time correcting the AI's output than you would have spent writing the code yourself. By month six, you are considering whether to scrap the whole thing and start over. I have seen this trajectory play out multiple times now, and the turning point is always the same: the foundation. The research supports this observation. Baytech Consulting's analysis of AI-assisted projects found that vibe-coded projects typically hit a "spaghetti point" around month three, where adding new features starts breaking existing ones and velocity drops to near zero. Projects built on solid foundations maintained steady velocity and eventually overtook the faster-starting projects in total throughput. The crossover point was consistent and predictable. Speed without structure is not speed. It is borrowed time. AI Speeds Up Whatever Direction You Are Heading If I had to distill everything in this article into a single sentence, it would be this: AI speeds up whatever direction you are already heading. If your codebase is clean, well-organized, and built on thoughtful architectural decisions, AI accelerates you toward an even cleaner, more capable system. Every feature the AI adds reinforces the existing patterns. Every test it writes follows the established conventions. The compounding effect works in your favor, and the returns increase over time. If your codebase is messy, inconsistent, and built on whatever worked in the moment, AI accelerates you toward a system that becomes progressively harder to understand, modify, and maintain. Every feature the AI adds introduces new inconsistencies. Every shortcut it replicates creates new debt. The compounding effect works against you, and the costs increase over time. This is not a theoretical distinction. It is the single most important practical reality of working with AI coding tools in 2026. The most important thing for an AI-powered codebase is not a smarter model. It is not better prompts. It is not a fancier IDE integration. It is a solid foundation. The first 1,000 lines of code determine the next 100,000. Make them count. A Practical Checklist for Starting Right For anyone about to start a greenfield project with AI coding agents, here is what I recommend based on everything I have learned: First, design your directory structure before writing any code. Make it self-documenting. Every folder should have an obvious purpose. The AI will use this structure as a map for where to put things, so make sure the map is clear. Second, write your CLAUDE.md or equivalent configuration file. Document your coding style, your architectural patterns, your testing conventions, and your API design principles. Be specific. "Use clean code" is useless. "All API endpoints return standardized error responses using the ErrorResponse class from src/shared/errors" is useful. Third, build your shared utilities first. Error handling, validation, logging, database patterns, authentication. These are the building blocks everything else depends on. Write them carefully, because the AI will reference them thousands of times. Fourth, implement one complete feature end-to-end as a reference. This gives the AI a concrete example of how all the patterns come together in practice. One well-built feature is worth more than a hundred pages of documentation. Fifth, establish strict code review practices from day one. Review the first features the AI generates with extra scrutiny. Make sure it picked up the right patterns. Correct any drift immediately, because every uncorrected drift becomes the new pattern. Sixth, resist the temptation to ship before the foundation is solid. Yes, it feels slow. Yes, stakeholders want to see features. But those two days you spend getting the foundation right will save you months of corrections later. The math is not even close. The age of AI-assisted development does not reward the fastest coders. It rewards the clearest thinkers. The ones who understand that what you build before the AI starts writing is more important than anything the AI will ever produce. That foundation is your competitive advantage. It is the difference between a codebase that gets better over time and one that slowly collapses under its own weight. Build it right. Build it once. Let the AI take it from there. What is your experience with AI coding foundations? Have you noticed the difference between working with AI in a clean codebase versus a messy one? I would love to hear your stories. Drop a comment below or reach out to me directly. If you found this useful, follow me for more insights on AI-assisted software development, engineering leadership, and building systems that last. You can find me on LinkedIn and Threads, or explore more articles right here on ivanturkovic.com. I write regularly about the intersection of AI and real-world software engineering, with a focus on what actually works in production, not what sounds good in a demo. If that resonates with you, stick around. There is a lot more to come. --- # How to Make Your Website Agent-Ready With WebMCP: A Practical Guide URL: https://www.ivanturkovic.com/2026/02/23/webmcp-tutorial-make-website-agent-ready/ Published: 2026-02-23 Topics: AI, AI-Driven Development In my previous post on WebMCP, I covered what WebMCP is and why it matters. The response told me something important: developers do not want another think piece about the future of the web. They want to know how to actually build with it. So this is the practical companion. No philosophy. No speculation about whether this will matter. Just code, setup instructions, and patterns you can apply to a real website today. The W3C specification was updated on February 12, 2026. Chrome 146 ships with a working DevTrial behind a feature flag. The MCP-B polyfill provides navigator.modelContext for browsers that do not support it natively yet. There are already framework integrations for React, vanilla JavaScript, and Ruby on Rails. The tooling is real, the documentation is solid, and there are working reference implementations you can study. Here is everything you need to make your website agent-ready. Setting Up Your Environment Before writing any WebMCP code, you need a browser that supports it. Right now, that means Chrome 146 with a feature flag enabled. Open Chrome 146 (Canary or later). Navigate to chrome://flags. Search for "Experimental Web Platform features" or "WebMCP for testing." Enable the flag and relaunch Chrome. That is it. Your browser now understands the navigator.modelContext API. For testing and debugging, install the Model Context Tool Inspector extension from the Chrome Web Store. This extension lets you view all registered tools on any page, test tool invocations manually, validate schemas, and monitor agent calls in real time. It is the DevTools equivalent for WebMCP, and you will want it open constantly while developing. If you want your tools to work across browsers that do not yet have native WebMCP support, use the MCP-B polyfill. One line, no dependencies: The polyfill automatically detects whether native browser support exists and only activates when needed. When Chrome ships native support, the polyfill steps aside. Your code does not change. The Declarative API: Agent-Ready Forms in Two Attributes The fastest way to make your site agent-ready is the declarative API. If you have an HTML form, you can make it a WebMCP tool by adding two attributes. That is not a simplification. It is literally two attributes. Here is a standard search form:
Here is the same form, now agent-ready:
The browser reads toolname and tooldescription and automatically generates a JSON schema from the form's input fields. Their names, types, validation rules, and required attributes all become part of the tool's input definition. The agent sees a structured tool called search_products, understands what it does from the description, and knows exactly what parameters to provide. When the agent invokes the tool, the browser brings the form into focus, populates the fields visually so the user can see what is happening, and waits for the user to click submit. The human stays in the loop by default. If you want to allow automatic submission without user confirmation, add toolautosubmit="true". Think carefully before doing this. Any action that modifies data, initiates a transaction, or has side effects should require explicit user confirmation. This is the kind of progressive enhancement the web was built for. Your form works exactly the same for human visitors. It just also works for AI agents now. WebMCP in Ruby on Rails If you are building with Rails, there is already a gem that integrates WebMCP directly into Rails form helpers. The webmcp-rails gem by Jesse Waites adds a webmcp: option to form_with, form_for, and all standard input helpers. No JavaScript. No separate configuration. Just idiomatic Rails. Install the gem: gem "webmcp-rails" Then use it in your views exactly the way you would expect: <%= form_with model: @product, webmcp: { tool: "create_product", description: "Create a new product listing", autosubmit: false } do |f| %> <%= f.text_field :name, webmcp: { param_description: "Product name" } %> <%= f.number_field :price, webmcp: { param_description: "Price in USD" } %> <%= f.text_area :description, webmcp: { param_description: "Detailed product description" } %> <%= f.select :category, ["Electronics", "Clothing", "Home"], webmcp: { param_description: "Product category" } %> <%= f.date_field :available_from, webmcp: { param_description: "Date when product becomes available", param_title: "Availability Date" } %> <%= f.submit "Create Product" %> <% end %> This renders standard HTML with the proper WebMCP attributes:
The gem supports all standard Rails form field helpers: text_field, email_field, password_field, number_field, date_field, datetime_field, time_field, url_field, telephone_field, search_field, text_area, color_field, range_field, hidden_field, and select. It requires Ruby 3.1+ and Rails 7.0+. What I particularly appreciate about this approach is that it stays completely within Rails conventions. You are not learning a new framework or adding a JavaScript layer. You are adding a hash option to helpers you already use every day. If you later decide WebMCP is not for you, removing the webmcp: options leaves your forms exactly as they were. Zero risk, zero lock-in. For a typical Rails application, the implementation strategy is straightforward. Start with your most trafficked forms: search, contact, signup, checkout. Add the webmcp: options. Deploy. Monitor. Then expand to more complex interactions using the imperative JavaScript API for actions that go beyond simple form submissions. Here is a more complete Rails example showing a todo application with search and creation forms: <%# Search form - read-only, safe for autosubmit %> <%= form_with url: todos_path, method: :get, webmcp: { tool: "search_todos", description: "Search todo items by keyword and status", autosubmit: true } do |f| %> <%= f.search_field :q, webmcp: { param_description: "Search keywords" } %> <%= f.select :status, ["all", "pending", "completed"], webmcp: { param_description: "Filter by status" } %> <%= f.submit "Search" %> <% end %> <%# Create form - requires user confirmation %> <%= form_with model: @todo, webmcp: { tool: "add_todo", description: "Add a new todo item" } do |f| %> <%= f.text_field :title, webmcp: { param_description: "Title of the todo" } %> <%= f.date_field :due_date, webmcp: { param_description: "Due date", param_title: "Due Date" } %> <%= f.select :priority, ["low", "medium", "high"], webmcp: { param_description: "Task priority level" } %> <%= f.submit "Add Todo" %> <% end %> Notice the distinction: the search form has autosubmit: true because it is a read-only operation. The create form omits it, requiring the user to confirm before the agent can submit. This pattern of read versus write permissions maps directly to how you should think about every WebMCP tool you expose. The Imperative API: Full JavaScript Control The declarative API covers straightforward form submissions. For anything more complex, stateful interactions, multi-step workflows, or dynamic logic, you need the imperative API. This gives you full programmatic control through navigator.modelContext.registerTool(). Here is the API shape from the February 12, 2026 W3C draft: if ('modelContext' in navigator) { navigator.modelContext.registerTool({ name: 'search_inventory', description: 'Search product inventory with filters', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search keywords' }, category: { type: 'string', description: 'Product category filter' }, maxPrice: { type: 'number', description: 'Maximum price in USD' }, inStock: { type: 'boolean', description: 'Only show items currently in stock' } }, required: ['query'] }, annotations: { readOnlyHint: true }, async execute(input, client) { const results = await fetch('/api/products?' + new URLSearchParams(input)); const data = await results.json(); return { content: [{ type: 'text', text: JSON.stringify(data) }] }; } }); } The execute function receives two arguments: input (the parameters the agent provided) and client (a ModelContextClient object). The client object gives you access to requestUserInteraction(), which lets you pause execution and ask for human confirmation before sensitive actions: navigator.modelContext.registerTool({ name: 'place_order', description: 'Place an order for items in the cart', inputSchema: { type: 'object', properties: { shippingMethod: { type: 'string', description: 'Shipping speed: standard, express, or overnight' } }, required: ['shippingMethod'] }, async execute(input, client) { const cart = await cartService.getCart(); const total = cart.calculateTotal(input.shippingMethod); const confirmed = await client.requestUserInteraction(async () => { return showConfirmationDialog({ items: cart.items, total: total, shipping: input.shippingMethod }); }); if (!confirmed) { return { content: [{ type: 'text', text: JSON.stringify({ status: 'cancelled' }) }] }; } const order = await orderService.place(cart, input); return { content: [{ type: 'text', text: JSON.stringify({ status: 'placed', orderId: order.id }) }] }; } }); For managing multiple tools at once, use provideContext() to register a batch and clearContext() to reset. You can also remove individual tools with unregisterTool('tool_name') when context changes, for example when a user navigates to a different section of your application. WebMCP in React For React applications, the @mcp-b/react-webmcp package provides a useWebMCP hook that handles tool registration and cleanup automatically through React's lifecycle: npm install @mcp-b/react-webmcp @mcp-b/global zod 'use client'; import { useWebMCP } from '@mcp-b/react-webmcp'; import { z } from 'zod'; function ProductSearch() { const [results, setResults] = useState([]); useWebMCP({ name: 'search_products', description: 'Search the product catalog', inputSchema: { query: z.string().describe('Search keywords'), category: z.string().optional().describe('Category filter'), maxPrice: z.number().optional().describe('Maximum price') }, handler: async ({ query, category, maxPrice }) => { const data = await productAPI.search({ query, category, maxPrice }); setResults(data.products); return { success: true, count: data.products.length, products: data.products.map(p => ({ id: p.id, name: p.name, price: p.price })) }; } }); return (
{results.map(product => ( ))}
); } The hook automatically registers the tool when the component mounts and unregisters it when the component unmounts. No manual cleanup. The tool exists exactly as long as the component that provides it. For Next.js with the App Router, remember that WebMCP hooks require 'use client' because they depend on browser APIs. Import the @mcp-b/global polyfill in a client component before any tools are registered, but do not make your root layout a client component. Writing Good Tool Descriptions This is the part most developers will underestimate, and it is arguably the most important part of the entire implementation. Your tool descriptions are how agents decide whether to use your service. They function like meta descriptions in SEO, except the stakes are higher because the agent does not just rank you, it either invokes you or ignores you entirely. Compare these two descriptions for the same tool: Weak: "Search stuff" Strong: "Search the product catalog by keyword. Returns matching products with name, price, availability, and thumbnail URL. Supports filtering by category and price range." The strong description tells the agent exactly what the tool does, what parameters are useful, and what to expect in the response. For parameter descriptions, be specific about formats: "Start date in YYYY-MM-DD format" is far more useful than "The date". The specification recommends keeping your tool count below 50 per page. Focus on your highest-value interactions rather than exposing everything your application can do. Testing and Debugging The Model Context Tool Inspector extension is your primary debugging tool. Install it, navigate to your WebMCP-enabled page, and you will see every registered tool, its schema, and description. You can invoke tools manually, pass test parameters, and inspect return values. Google has also published a WebMCP Evals CLI for automated testing and a live reference demo at travel-demo.bandarra.me that demonstrates both APIs. Study the demo. It covers flight search, hotel filtering, and itinerary building and serves as an excellent reference implementation. What to Implement First Start with read-only tools. Product search, store locators, pricing lookups. They do not modify state, so security concerns are minimal. Set readOnlyHint: true in annotations. Next, add WebMCP attributes to your most common forms. Contact, search, signup, support tickets. You are adding two HTML attributes or one Rails hash option. Then build imperative tools for complex workflows. Multi-step checkout, booking flows, configuration wizards. Use requestUserInteraction() for anything that modifies data or spends money. Finally, monitor everything. Track which agents call your tools, success rates, error patterns, and execution times. What to Watch Out For WebMCP is still a DevTrial. The API surface will change. Build prototypes. Do not ship mission-critical functionality on an unstable API. The security model is incomplete. The MCP ecosystem has already seen real-world prompt injection attacks, tool poisoning, and data exfiltration. Never expose administrative actions without requestUserInteraction(). Validate all inputs as untrusted data. Treat agent-provided parameters with the same skepticism you would apply to any public API endpoint. Tool discovery remains an unsolved problem. Agents can only find your tools after navigating to your page. There is no manifest-based discovery yet. This will be addressed in future versions of the specification. The Ecosystem Is Moving Fast Within two weeks of the February 10 announcement, there are framework integrations for Rails, React, and vanilla JavaScript. A polyfill with full API compatibility. A Chrome debugging extension. A CLI testing tool. Reference demos. An active community building examples from todo apps to travel insurance platforms. The webmcp-rails gem, the @mcp-b/react-webmcp package, and the @mcp-b/global polyfill all work today. The W3C working group includes engineers from Microsoft and Google. The awesome-webmcp repository on GitHub catalogs a growing list of resources. The developers who build familiarity with navigator.modelContext now will be ready when Chrome 146 hits stable around March 2026. Everyone else will be reading tutorials later, wondering why their competitors' sites show up in agent workflows and theirs do not. Final Thoughts If this kind of hands-on engineering content is useful to you, consider following me for more. I write about AI, software architecture, and the real-world engineering decisions that do not make it into marketing slides, drawing from over two decades of building systems in fintech, blockchain, and high-traffic environments. You can find me at ivanturkovic.com, on LinkedIn, and on Threads. If you are implementing WebMCP and want to compare notes, or if you have found something I missed, I would genuinely like to hear from you. What tools are you planning to expose first? Have you already started experimenting with the DevTrial? Let me know in the comments or reach out directly. The best engineering insights come from practitioners comparing real implementation experiences. --- # Coding Is Changing. Software Engineering Is Not Going Anywhere. URL: https://www.ivanturkovic.com/2026/02/22/coding-changing-software-engineering-not-obsolete/ Published: 2026-02-22 Topics: AI, AI-Driven Development, Software Engineering At the World Economic Forum in Davos on January 20, 2026, Anthropic CEO Dario Amodei said something that landed like a grenade across the technology industry. In an interview with The Economist, he claimed that AI models could do "most, maybe all" of what software engineers currently do within six to twelve months. He pointed to engineers at Anthropic who told him they no longer write code from scratch at all. "I just let the model write the code, I edit it," he said they told him. The reaction was immediate and predictably polarized. Panic in developer forums. Dismissal from senior engineers. Viral posts alternating between "we're cooked" and "he's wrong and here's why." Zoho founder Sridhar Vembu went further than Amodei, publicly advising software developers to begin considering alternative livelihoods. Elon Musk posted about building an AI-based software competitor to Microsoft. The discourse reached the kind of intensity that guarantees more heat than light. I want to offer a different reading of this moment. Not a dismissal of what Amodei said, and not a panic-driven response to a headline that almost certainly oversimplifies what he actually meant. A careful, honest look at what is actually changing, what is not, and what this shift means for the engineers, architects, and technical leaders navigating it in real time. Because I think Amodei is right about something important, and importantly wrong about something else. And the distinction between those two things matters enormously for how software engineers should be thinking about their careers and their craft right now. What Amodei Is Right About Let me start with what is actually true in this claim, because dismissing it entirely would be as intellectually dishonest as accepting it uncritically. The productivity of AI coding tools has crossed a genuine threshold. This is not incremental improvement. In the past eighteen months, the gap between what an experienced engineer can produce with AI assistance and what they could produce without it has widened from meaningful to substantial. Prototypes that took days now take hours. Boilerplate that consumed genuine cognitive energy now gets generated correctly on the first attempt. Test coverage that teams perpetually deferred because of the time cost is now achievable within the scope of normal development cycles. Anthropic's own CEO noted in a January 2026 essay that AI models have reached a stage where some of the strongest engineers he knows hand over almost all of their coding work to AI, contrasting this with just three years earlier when AI struggled with elementary arithmetic and could barely write a single line of code. That trajectory is real. I have experienced it directly. The tools available today are meaningfully more capable than the tools available twelve months ago, and there is no obvious reason to expect that rate of improvement to slow. AI researcher Andrej Karpathy, who coined the term "vibe coding" a year ago, has since noted that the approach has evolved: programming via AI agents is "increasingly becoming a default workflow for professionals, except with more oversight and scrutiny." He now prefers the term "agentic engineering," reflecting a genuine shift in how developers interact with code rather than just an incremental improvement in autocomplete. So yes: the way code gets written is changing dramatically. Senior engineers at capable AI companies are legitimately spending less time writing code line by line. That observation is accurate, and engineers who resist acknowledging it are not doing themselves any favors. Where the Claim Breaks Down Here is where the reasoning goes wrong, and it goes wrong at a definitional level that compounds into a significant error. Dario Amodei is one of the most technically sophisticated people in the industry. He understands AI deeply. But "software engineering" and "writing code" are not the same thing, and the example he offered to illustrate his point, engineers who no longer write code and instead edit AI-generated output, does not demonstrate that software engineering is becoming obsolete. It demonstrates that one component of software engineering, the act of translating decided solutions into working syntax, is being automated. That component was never the hard part. The hard part of software engineering has always been the thinking that precedes and follows the writing. What problem are we actually solving? What are the constraints, stated and unstated? What are the failure modes of this approach under load, under adversarial input, under the organizational conditions that will exist when this system is maintained two years from now by people who were not in the room when it was designed? How does this component interact with the rest of the system? What do we need to leave out of version one without making version two impossible? These questions do not become easier when AI generates the code. In many cases they become harder, because the gap between "AI produced something that passes tests" and "AI produced something that is correct for this specific context" requires exactly the kind of deep understanding that experienced engineers develop over years of making decisions and living with their consequences. I have spent more than twenty years building and operating production systems across fintech, blockchain, and high-traffic platforms. In my experience, the most common source of expensive engineering failures is not bad code. It is decisions made without adequate understanding of the domain, the system boundaries, the security model, or the long-term maintenance implications. AI tools make the code-writing faster. They do not make those decisions for you. They cannot, because those decisions require context that lives in the organization, the regulatory environment, the product strategy, and the engineering team's accumulated understanding of how their specific system behaves under real conditions. The Confusion Between Coding and Software Engineering This is the distinction that I think is most important to articulate clearly, because it is the one most consistently collapsed in these conversations. Coding is the act of expressing a decided solution in a programming language. It requires syntax knowledge, familiarity with language idioms, understanding of the tools and frameworks in use, and the mechanical skill of translating intent into instructions a computer can execute. This is genuinely what AI tools are getting very good at. The translation layer between "here is what we want to happen" and "here is code that makes it happen" is being automated at an impressive rate, and that rate will likely continue to increase. Software engineering is something much larger. It includes understanding what the system needs to do before anyone has specified it precisely. It includes architecture: the decisions about how components relate to each other, what responsibilities they hold, how they communicate, and how they will evolve as requirements change. It includes security modeling: identifying what needs to be protected, what the realistic threat model looks like, where the boundaries between trusted and untrusted input exist. It includes scalability thinking: how does this system behave when the load doubles, when the data volume grows by a factor of ten, when the number of concurrent users exceeds the assumptions made during design? It includes the organizational dimension of technology: how do you build systems that teams can understand, maintain, and extend over time? How do you make decisions that survive the departure of the people who made them? How do you document not just what a system does but why it was designed that way? How do you build the shared understanding that allows a team to make coherent decisions over years without the original architects in the room? None of these are coding problems. None of them become obsolete when AI generates code more efficiently. Some of them actually become more important as AI-generated code becomes more prevalent, because the risk of mistaking "the code runs" for "the system is correct" grows when the code was produced at speed by a tool that has no understanding of the broader context. What Is Actually Changing, and Why It Matters I want to be careful not to minimize the genuine change that is happening, because minimizing it would be a different kind of error. The role of software engineers is shifting. That shift is real, it is already underway, and it will accelerate. Engineers who spend most of their time on work that AI can now do competently will face genuine pressure. Entry-level positions that were primarily about coding are changing in character. The path from junior engineer to senior engineer, which historically involved accumulating years of experience writing code, is going to look different as the code-writing component becomes something that AI handles with increasing competence. But "the role is changing" and "the role is becoming obsolete" are not the same claim, and confusing them leads to conclusions that are not useful for the engineers trying to navigate this moment. What the shift actually looks like, from my perspective and from the experience of the engineers I work with and observe, is that the work of software engineering is moving up the abstraction stack. The questions that senior engineers have always spent time on, but often could not spend enough time on because the code had to be written by someone, are becoming more central to the role. Those questions include the ones I described above: architecture, security, scalability, organizational knowledge management, product alignment. They also include a new category of questions specific to this moment: how do you effectively direct AI tools to produce good software? How do you review AI-generated code with the critical eye necessary to catch what the AI got wrong? How do you integrate AI-assisted development into workflows that preserve the institutional knowledge and architectural coherence that good systems require? These are genuinely hard questions. They require more engineering sophistication, not less. The engineers who will be most valuable in this environment are not the ones who resisted AI tools, and they are not the ones who trusted AI tools uncritically. They are the ones who understand their systems deeply enough to know when AI-generated code is right and when it is plausible-looking but wrong. The Argument from Anthropic's Own Engineers I want to address directly the example Amodei used to support his claim, because it is both the most compelling part of his argument and the most misleading. He said that engineers at Anthropic told him they no longer write code, they just let the model write it and edit the result. This is real, and it reflects something genuine about how work has changed at the frontier of AI development. But Anthropic's engineers are not a representative sample of software engineering, and their experience at a company building the tools themselves does not generalize straightforwardly to the broader industry. The engineers at Anthropic who are editing rather than writing are still making the architectural decisions that determine what the AI generates. They are still specifying the behavior, reviewing the output for correctness, catching the cases where the model produced something that looks right but is subtly wrong, and making the judgment calls about when the generated code is acceptable and when it needs to be directed differently. That work requires deep engineering expertise. It is not coding in the traditional sense, but it is absolutely software engineering. There is also a selection effect worth noting. The engineers at Anthropic are working on systems where the tooling is optimized for exactly this workflow, where the code generation capabilities are at the frontier, and where the organizational context supports this mode of work in ways that most companies are not yet set up to replicate. The experience of an Anthropic engineer editing AI-generated code for an AI system is not the same as the experience of an engineer at a financial institution trying to use AI tools to modify a fifteen-year-old codebase with complex regulatory requirements and inadequate documentation. Why I Am Actually Optimistic About This Shift Here is the perspective that I think gets lost in the panic: the change that Amodei is pointing to, if understood correctly, is one that most experienced engineers should genuinely welcome. The parts of software engineering that are being automated are, in many cases, the parts that experienced engineers found least interesting and most draining. Writing boilerplate. Translating known patterns into language-specific implementations. Producing test cases for well-specified behavior. Documenting code whose function is straightforward. These tasks consumed real time and cognitive energy that could have gone toward more interesting and more valuable work. The parts of software engineering that are not being automated, the architecture, the security modeling, the product alignment, the organizational and leadership dimensions, the deep domain understanding that produces good systems rather than just working ones, are the parts that skilled engineers have always found most engaging and most valuable. A shift that moves more of engineering time toward those tasks and away from mechanical implementation is not obviously bad for people who got into this field because they wanted to solve hard problems. I am genuinely glad that software engineers will spend more time on product vision, technical scalability, and software stability. Those are areas that have historically been chronically underinvested in, not because they were less important than coding, but because the coding demanded so much time that the thinking was often deferred. If AI tools give engineering teams the capacity to do more of the thinking, and less of the mechanical translation of that thinking into syntax, that seems like a net improvement for the quality of software that gets built. The engineers who will struggle in this environment are the ones whose value was primarily in their ability to write code quickly and correctly, and who have not developed the deeper capabilities that good software engineering requires. That is a real challenge, and it is worth taking seriously. But it is a challenge about which skills to develop, not a signal that the profession is disappearing. What Engineers Should Actually Be Doing Right Now If you are a software engineer watching this conversation and wondering what it means for your career, here is my honest advice based on two decades of watching the industry evolve through multiple waves of tools and platforms that were supposed to change everything. Invest in the capabilities that AI cannot replicate. Deep domain understanding in your area. Architectural thinking. Security modeling. The ability to reason about systems under failure conditions. The organizational skills of building shared understanding, making decisions legible, and managing technical complexity in ways that teams can navigate over time. These are the capabilities that have always separated good engineers from exceptional ones, and they are the capabilities that will matter most as the code-writing layer becomes more automated. Become genuinely good at directing and reviewing AI-generated code. This is not the same skill as writing code, and it is not trivially easier. It requires understanding what AI tools do well, where they make characteristic mistakes, and what kinds of review are necessary to catch the cases where output looks correct but is not. This is a new skill, and it is one that will differentiate engineers who are effective in AI-assisted workflows from those who are not. Invest in understanding the broader context of the systems you work on. The engineers who are most valuable in AI-assisted development environments are the ones who understand the business domain, the regulatory environment, the organizational constraints, and the product strategy well enough to direct AI tools effectively and review their output critically. That understanding comes from engagement with the broader context, not just from mastery of the code. Resist the binary framing of this conversation. The choice is not between "AI replaces software engineers" and "nothing is changing." The actual situation is more nuanced and more interesting: the tools are changing, the workflows are changing, the mix of skills that is most valuable is changing, and the engineers who navigate that change thoughtfully will be more valuable, not less. A Final Word on Predictions at Davos It is also worth noting, without dismissing Amodei's observation, that the World Economic Forum in Davos has a long and somewhat undistinguished history as a venue for confident predictions about technological transformation that turn out to be premature, overstated, or simply wrong in their timeline. Amodei himself admitted he was not completely certain how fast the transition will happen, noting that some components, like chip manufacturing and model training, cannot yet be automated. The hedged version of his claim is more defensible than the headline version, and the headline version is what drove most of the reaction. The gap between "AI is getting very good at generating code and this will change how software engineers work" and "software engineering will be completely obsolete in six to twelve months" is large. The first claim is clearly true. The second is almost certainly not. Understanding the difference between them is the kind of precise thinking that, incidentally, is exactly what good software engineers are trained to do. The discipline is not going anywhere. The nature of the work is changing. That is a distinction worth holding onto, especially when the headlines are generating more heat than the underlying reality warrants. This is a topic I think about constantly and will continue writing about as the AI and software engineering landscape develops. If you have thoughts, disagreements, or experiences from your own team that push back on this analysis, I genuinely want to hear them. Follow along on LinkedIn or reach me through the contact page. And if this post resonated with how you are thinking about your career or your team, share it with someone who is navigating the same questions. The comments section below is open. --- # The “Build It Yourself” Trap: How AI Enthusiasm Is Quietly Killing Core Product Development URL: https://www.ivanturkovic.com/2026/02/21/build-it-yourself-trap-ai-tooling-vs-core-product/ Published: 2026-02-21 Topics: AI, AI-Driven Development, Software Engineering A new kind of logic is spreading through developer communities, startup circles, and engineering Slack channels. It goes something like this: why pay a monthly subscription for software someone else built when you now have access to AI coding assistants powerful enough to help you build it yourself? The reasoning sounds compelling on the surface. The economics look attractive in a spreadsheet that has not been filled in all the way. And the energy behind it is real, driven by genuine breakthroughs in what AI tools can do. But there is a significant gap between what AI coding tools make possible and what they make wise. And in my experience building and operating real systems across fintech, blockchain, and high-traffic platforms over the past two decades, I have seen this exact pattern before, dressed in different clothing. This post is not a defense of every SaaS product that has ever existed. Many deserve to be replaced. Some are overpriced, underbuilt, and held together by aggressive pricing locked behind annual contracts. But the current wave of enthusiasm for building everything yourself, powered by AI assistants and a three-weekend sprint, deserves serious scrutiny before engineering teams and founders stake their infrastructure on it. How a Reasonable Idea Becomes an Expensive Distraction The sequence tends to follow a recognizable pattern. It starts with a legitimate grievance. A SaaS tool your team has been using raises its prices. Or adds a feature behind a higher tier that you need. Or the product has simply not improved in two years while your requirements have grown. The frustration is valid. You open a conversation about alternatives and someone on the team points out that with AI coding assistance, building a replacement looks genuinely feasible. The conversation gets exciting. Engineers are curious people. The idea of owning your own stack, having exactly the features you need, and eliminating a vendor relationship has real appeal. You sketch out what the custom version would look like. It seems manageable. Someone volunteers to spend a weekend on a proof of concept. The proof of concept works, or at least it works well enough to generate momentum. The happy path is solid. The AI assistant produced clean-looking code faster than anyone expected. Leadership approves continuing the effort. The team is energized. Three weekends in, you have something that feels like a real product. Then reality begins its slow arrival. The edge cases surface. The timezone handling is wrong for users in certain regions. The export functionality breaks for datasets above a certain size. The permission system that looked complete in the prototype turns out to need five more states than anyone anticipated. Each of these is fixable. Each fix takes time. The engineer who built the prototype is now the de facto owner of an internal tool, and that ownership is expanding to consume more of their week than the original estimate suggested it would. Meanwhile, the core product features that were on the roadmap before this effort began are still waiting. The customers who were expecting them are still waiting. The competitive pressure that made those features urgent has not paused while your team debugged an internal invoicing edge case at 2am. This is the trap. Not the technical challenge of building the tool, though that is harder than it looks. The trap is the attention and focus it consumes from the work that actually moves your business forward. The Incomplete Economics of the "Cancel SaaS" Argument The financial case for building your own tooling almost always looks better at the start of the conversation than it does twelve months later. This is not because people are being dishonest. It is because the costs that matter most are the ones that do not appear until after the decision has been made. The initial calculation is straightforward: what you pay for SaaS subscriptions versus what it would cost to build a replacement. That comparison favors building, at least on the surface. AI coding tools are expensive, but many teams already have them. Cloud infrastructure has real costs, but teams that are already running production systems have existing infrastructure they assume they can leverage. The SaaS subscription fees stand out as the obvious line item to eliminate. What does not appear in that initial calculation: the engineering hours required to build the first working version, including the iteration after the prototype reveals what was missed. The hours spent on security review before you can responsibly put real business data through a custom-built system. The ongoing hours for dependency updates, monitoring, incident response, and the feature requests that accumulate once internal users have something that mostly works and start asking for the ten percent that does not. The most significant cost that almost never makes it into the initial calculation is the one that should weigh most heavily on any engineering leader: the cost of the product work that does not happen. Senior engineers are the scarcest resource in most technology companies. Every hour a senior engineer spends maintaining an internal tool is an hour not spent on the capability that differentiates your product in the market. That is not a zero cost. In many cases, it is the highest cost in the entire analysis, and it never appears on the spreadsheet where the SaaS subscription fees are listed. I have seen this calculation play out in multiple organizations. A team replaces three SaaS tools over the course of six months. The annual subscription savings are real: perhaps sixty thousand dollars. What does not appear in the accounting is that the engineers who built and now maintain those tools contributed meaningfully less to the core product during that period, and the product fell further behind competitors who kept their engineering attention focused on what customers actually paid them to build. The true cost of that gap is not sixty thousand dollars. It is almost always larger. What a SaaS Subscription Actually Pays For There is a persistent misunderstanding in the "build it yourself" conversation about what you are actually buying when you pay for a mature SaaS product. The assumption is that you are paying for software, and that the software is the primary thing of value. This framing makes the comparison to building your own seem straightforward: if the software can be replicated, the cost can be eliminated. But in most cases, the software is not primarily what you are paying for. You are paying for a running service, operated continuously by a team whose entire professional focus is making that one thing work reliably for people whose businesses depend on it. Consider what is included in a subscription to a mature payment processing platform. The software that accepts and routes transactions is only the beginning. Behind it is PCI compliance infrastructure that required years and significant capital to achieve and certify. Fraud detection models trained on hundreds of millions of real transactions. Customer support for your end users when something fails. Uptime SLAs backed by on-call engineering teams who are responsible for incidents, not you. Relationships with card networks that took years to establish and are not available to a team that built its own payment system last month. Consider an email delivery service. The software that sends emails is genuinely not the valuable part. The valuable part is IP reputation built over years of careful management, deliverability relationships with major inbox providers that you cannot replicate by writing code, bounce and complaint handling infrastructure, and compliance tooling for a regulatory landscape that changes continuously. A team that decides to build its own email sending infrastructure typically discovers the real cost of that decision when their first campaign lands in spam for seventy percent of recipients and they have no obvious path to fix it. This does not mean every SaaS product is this valuable. Some are thin wrappers around open-source libraries with pricing that reflects sales team ambition rather than genuine product depth. Those deserve to be scrutinized, and in some cases replaced. But the evaluation should be honest about what you are taking on when you decide to own the problem yourself, including which of these categories the specific tool you want to replace actually belongs to. The Security Responsibility Nobody Volunteers For AI coding tools generate plausible-looking, functional code very quickly. They have been trained on enormous amounts of existing code, which means they have also absorbed the security vulnerabilities present in that code. Research into AI-generated code has found vulnerability patterns appearing at rates that are not meaningfully better than human-written code, and sometimes worse, depending on the domain and the specific patterns involved. Common vulnerability categories appear regularly in AI-generated code: SQL injection, broken access control, insecure direct object references, improper input validation, hardcoded secrets. These are not exotic vulnerabilities requiring sophisticated attacks to exploit. They are the ones that appear in breach reports year after year because they are straightforward to find and exploit once they exist in a production system. The more fundamental issue is not whether AI-generated code contains these vulnerabilities. It is what happens when they are discovered. A mature SaaS product has a security function, a vulnerability disclosure process, the ability to patch all customers simultaneously, and some history of having been reviewed by people whose job is to find problems before attackers do. When a vulnerability is found in a mature SaaS product, the vendor patches it and notifies customers. When a vulnerability is found in your custom-built internal tool, you are the vendor. You patch it, if you catch it before someone else does. You notify affected users, if notification is required. You are responsible for understanding the scope of exposure, preserving the right forensic evidence, and managing any regulatory or legal obligations that follow. This is not hypothetical overhead. It is a realistic scenario for any system that handles real business data, and it arrives without warning. I have spent enough time in fintech to have seen how security incidents in custom-built systems unfold. The pattern is consistent: the system was built with genuine care, the engineers were competent, and the vulnerability was something that did not seem important at the time it was introduced. The cost of managing the incident, in engineering hours, legal fees, customer trust, and regulatory attention, invariably exceeds the cost of the SaaS subscription that the custom system was built to replace. Sometimes by a considerable multiple. The Invisible Organizational Cost: Knowledge That Lives in One Person Custom software creates organizational dependencies that are often not visible until they become problems. When a team builds an internal tool, the knowledge of how that tool works, why certain decisions were made, what edge cases were handled and which were deliberately deferred, lives primarily in the people who built it. That knowledge is not fully captured in the code, even when the code is well-commented. It is not fully captured in documentation, even when the documentation is unusually thorough. It lives in the intuition and memory of the engineers who sweated through the hard decisions. This creates a fragility that grows quietly over time. The engineer who built the system moves to a different team or a different company. The person who inherits the system has the code and the documentation, but not the reasoning. When something breaks in a non-obvious way, debugging it requires reconstructing context that the original engineer carried in their head. That reconstruction takes time, often at the worst possible moment. AI-assisted development makes this specific problem somewhat more acute. Code produced through rapid AI-assisted sprints tends to be code that the team understands at the level of "it works and the tests pass." The deeper understanding, the architectural reasoning, the deliberate choices about what not to support and why, these are less thoroughly embedded in teams that built quickly rather than slowly and carefully. When the rapid sprint produces the first version and then the engineer moves on, the organizational knowledge gap is larger than it would have been for the same system built through a more deliberate process. The SaaS tools being replaced have accumulated organizational knowledge of their own, embedded in documentation, support resources, community knowledge, and the product decisions of teams that have been thinking about nothing else for years. When you cancel the subscription, that accumulated knowledge becomes inaccessible. You are trading a known knowledge base for a system whose knowledge base exists primarily in the heads of the people who built it last month. The Core Product Problem: What Is Actually Getting Built? Here is the question that engineering leaders are not asking directly enough in these conversations: if your team spends the next six months replacing your operational tooling with custom-built alternatives, what does not get built? This question has a concrete answer in every organization, and the answer is almost always more important than the question of whether you can save on SaaS subscriptions. There are features your customers have been requesting. There are architectural improvements that would meaningfully improve system reliability. There are integrations that would open new revenue channels. There are performance problems that are degrading user experience in ways that your competitors do not share. All of these represent genuine business value that engineering effort can create. When engineers spend their capacity building and maintaining internal tooling instead, that business value does not get created. The customers waiting for the feature keep waiting. The architectural debt continues accumulating. The competitors who kept their engineering teams focused on their core products continue pulling ahead. The software that runs your business operations is, for most companies, not the software that differentiates your business in the market. Your invoicing system, your internal support tool, your project management workflow, your employee directory: these are important, and they need to work reliably, but they are not why your customers chose you over the alternatives. Treating them as worthy of significant engineering investment, especially at the expense of the product that actually creates value for customers, is a strategic confusion about what your engineering team is actually for. The irony is that the AI coding tools driving this enthusiasm could be directed at the core product instead. The same capability that allows a team to build a custom project management tool in three weekends could be used to accelerate the development of the features that customers are actually paying for. The productivity gain is real in both directions, but only one direction creates competitive advantage. I watched a fintech team go through exactly this choice over the course of a year. They had a genuine frustration with the pricing of several tools they used for internal operations, and they had genuine enthusiasm about what their engineers could build with AI assistance. They spent eight months building custom replacements for five SaaS tools. The replacements worked. The subscription costs were eliminated. And when the year-end product review happened, they had shipped fewer customer-facing features than in any previous year, and a competitor had filled a gap in the market that they had intended to address. The custom tools were good. The product fell behind. The trade-off was never made explicit, but it was made. Why This Moment Feels Different From Previous Cycles The argument that engineers should build rather than buy their operational tooling is not new. It has appeared in different forms at each major inflection point in developer capability. Each time, the argument has been partially right about the new capabilities and partially wrong about what remains hard regardless of the tools available. The CASE tools of the 1980s and 1990s promised that visual modeling would generate production software, eliminating the need for much of what external vendors sold. The capability was real in narrow domains and failed in broader ones because the complexity of production software is not primarily in the writing. It is in the maintenance, the edge cases, the operational reliability, and the accumulated decisions made over years of real-world use. Offshore outsourcing in the 2000s promised that cheap labor markets would make it cost-effective to build everything custom. The capability was real for certain categories of well-specified work and failed for others because specification quality turned out to be the constraint, and specification quality requires the same engineering judgment that you were supposedly eliminating the cost of. No-code and low-code platforms in the 2010s promised that visual development would make it feasible for non-engineers to build what previously required engineering teams. The capability was real for certain internal tools and simple workflows. The category found its genuine scope and settled into it. Enterprise software requiring real complexity still required real engineers. AI coding assistants are more powerful than any of these predecessors. The gap between what they can produce and what CASE tools generated is not marginal. It is substantial. But the fundamental shape of the problem they are being asked to solve has not changed. Production software is not primarily a writing problem. Writing the initial version is the cheapest part. Operating it, securing it, maintaining it, extending it, and doing all of that without consuming the engineering attention that should go toward your actual product: these are still where the real cost lives. What is genuinely different this time is the emotional momentum. The enthusiasm for AI-assisted development is real and justified by real capability. That enthusiasm creates a pull toward using the capability, even in contexts where using it displaces work that matters more. Recognizing that pull, and making deliberate choices about where to direct it, is the engineering leadership challenge of this particular moment. When Building Your Own Tooling Is Actually the Right Call I do not want to argue that building custom internal software is never the right decision. There are clear circumstances where it is not just acceptable but necessary, and where the strategic logic is sound even after accounting for all the costs. When the internal tool is adjacent to your core product competency, building it may make sense. If you are a data infrastructure company and you need a custom monitoring tool, building it exercises and develops capabilities that are directly relevant to your product work. The maintenance cost is not pure overhead; it is operational product development that feeds back into what you know and build for customers. When no adequate external option exists, building is necessity rather than preference. Some industries and use cases are genuinely underserved by the existing SaaS market. I encountered this repeatedly in blockchain and digital finance, where the tooling assumptions of mainstream SaaS products were built for traditional financial infrastructure that did not map to decentralized systems. In those cases, custom development was not a cost-saving exercise; it was the only viable path. When data sovereignty or regulatory requirements genuinely preclude external services, you build and own the system regardless of the cost, because the alternative is not being able to operate in your regulatory environment at all. When a specific SaaS product has become genuinely extractive, providing minimal value while extracting significant cost, the replacement analysis may still justify building. But it needs to be a real analysis, accounting for total cost of ownership including ongoing maintenance, security, and opportunity cost, not just a comparison of subscription fees to prototype build time. In all of these cases, AI coding tools genuinely help. They accelerate the initial build, reduce the cost of iteration, and make it feasible for smaller teams to own and maintain systems that previously required larger ones. The productivity improvement is real and should be applied where it matters. The question is whether it is being applied to work that creates business value or work that displaces the creation of business value. The Market Signal Worth Reading Correctly There is a genuine market signal in the current enthusiasm for building over buying, and it is worth reading correctly rather than dismissing. The signal is not that SaaS as a model is dying. It is that the competitive pressure on SaaS products has meaningfully increased, and the products that were coasting on the difficulty of building alternatives are about to face real consequences. For the past decade, many SaaS products operated in markets where the switching cost was not just the subscription fee but the engineering effort required to build an alternative. That effort was high enough to make many products defensible even when they were not particularly good. AI coding tools have lowered that effort, which means that defensibility based primarily on build difficulty is eroding. The SaaS products that survive this shift will be the ones that were genuinely valuable rather than merely difficult to replace. Products with deep domain expertise embedded in years of product decisions. Products with compliance infrastructure that is genuinely costly to reproduce. Products with network effects that make the shared platform more valuable than any individual instance. Products with operational reliability that comes from teams whose entire focus is a single problem. Some SaaS products will face real pressure and deserve to. The ones that have not invested in genuine product depth, that have relied on switching costs rather than value creation, will find the current environment clarifying. That pressure will improve the market for everyone, producing better products at more defensible price points. But this is a market correction that plays out at the level of product strategy, not a signal that every engineering team should immediately redirect their capacity toward building internal tooling. Understanding the difference between those two things is part of what engineering leadership is for. A Framework for Making the Decision Without Fooling Yourself If you are evaluating whether to build a custom alternative to a SaaS tool your team currently uses, here is the framework I would apply. It is based on two decades of making these decisions and living with their consequences. Start by being honest about the strategic category. Is the tool you want to replace adjacent to your core product competency, or is it genuinely commodity operational software? If it is commodity, the bar for building it yourself should be high, because the ongoing maintenance cost will fall on engineers whose time has a higher-value alternative use. Calculate the full build cost, not the prototype cost. The prototype gets you to a working happy path. The full build gets you to a system you can put real business data through, with real error handling, real security review, real monitoring, and real coverage of the edge cases that will surface in production. That typically costs three to five times what the prototype took. Budget that honestly before comparing to the subscription fee. Calculate the ongoing maintenance cost with a realistic ownership model. Who maintains this system? What is their fully-loaded hourly cost? How many hours per month will ongoing maintenance require, realistically? Include dependency updates, security patching, incident response, and the feature requests that will come once internal users have something that mostly works. This number is almost always larger than teams estimate at the start. Account for the opportunity cost explicitly. This is the step that most teams skip and should not. Write down, specifically, what product work will not happen if the engineers who would build and maintain this tool spend that capacity on internal tooling instead. If you cannot write down a specific answer, you have not thought about it carefully enough. If the specific answer is "features that customers have been requesting for two quarters," that is a concrete cost that should appear in your analysis. Account for the security and compliance responsibility. Does the tool handle sensitive data? What is your plan when a vulnerability is discovered? What regulatory obligations apply? Who owns those responsibilities on an ongoing basis? These are not hypothetical questions; they are operational realities that begin the day the custom system goes into production. If the analysis still favors building after all of those inputs have been considered honestly, then build with confidence and build well. Use AI tools to accelerate the work. Use them to write tests, to explore implementation options, to generate the boilerplate that would otherwise slow you down. But treat what you are building with the seriousness it deserves, because you are committing to own it for a long time. The Judgment That AI Cannot Replace The build-versus-buy conversation is ultimately a product strategy question, not a technical question. It is a question about where to direct the scarcest resource your organization has, which is the focused attention of engineers who understand your domain well enough to build good software in it. AI coding tools change some of the economics of that question by reducing the cost of writing the initial version of a system. They do not change the cost of operating it, securing it, maintaining it, or the opportunity cost of the engineering attention it consumes. They do not change the fact that the time your engineers spend on commodity operational tooling is time they are not spending on the work that actually creates competitive advantage. The engineering leaders who will make the best decisions in this environment are not the ones who are most enthusiastic about what AI tools can produce. They are the ones who remain disciplined about where that production capability should be directed. They ask not just "can we build this?" but "should we build this, and what does not get built if we do?" That judgment has always been the constraint. The tools have changed. The judgment required to use them well has not. Build what differentiates you. Buy what does not. And be honest about which is which before the sprint begins. If this analysis connects with how you think about engineering strategy, I write regularly on these topics at ivanturkovic.com. You can follow along on LinkedIn or reach me through the contact page if you want to discuss a specific situation your team is navigating. I am genuinely curious how different organizations are thinking through these trade-offs right now, particularly in fintech and product-led companies where engineering focus is a direct input to business outcomes. Share your experience and perspective in the comments below. --- # When ADD Is Wrong: Recognizing the Limits URL: https://www.ivanturkovic.com/2026/02/20/when-add-is-wrong-recognizing-limits/ Published: 2026-02-20 Topics: AI-Driven Development, Career & Leadership, Software Engineering Every methodology has boundaries. Waterfall fails when requirements change frequently. Agile struggles with fixed-scope contracts. TDD is awkward for exploratory prototyping. No approach works everywhere, and pretending otherwise leads to poor outcomes. ADD is no exception. There are tasks where AI-Driven Development is not the right approach. Recognizing these situations is a skill that separates effective practitioners from those who try to force every problem into the same solution. This post is an honest assessment of where ADD falls short and when you should step away from AI assistance and write code yourself. Tasks Requiring Deep Innovation AI models generate code by recombining patterns from training data. They are excellent at applying known approaches to new contexts. They are not sources of genuine innovation. When you need something truly novel, AI cannot help. New algorithms, new architectural patterns, unprecedented approaches to unsolved problems. These require human creativity that AI cannot provide because the patterns do not exist in training data. Signs you need innovation, not generation: The problem has no established solution approach Existing solutions do not work and you need a fundamentally different approach You are trying to do something that has not been done before The value comes from the novelty itself, not from execution of known patterns In these situations, AI assistance can actually be counterproductive. The AI will generate something that looks like a solution but is actually a remix of approaches that do not apply. You waste time evaluating plausible but wrong suggestions instead of doing the creative thinking the problem requires. When innovation is needed, do the creative work yourself. Once you have a novel approach, ADD may help with implementation details. But the innovation itself must be human. Security-Critical Implementations Security-critical code has two characteristics that make ADD risky: the cost of failure is high, and the failure modes are subtle. Cryptographic implementations, authentication core logic, authorization boundaries, security protocol handling. These areas require expertise that AI does not reliably possess. More importantly, the evaluation skills needed to verify security-critical AI output are specialized. Unless you are a security expert, you cannot reliably evaluate whether AI-generated security code is correct. The failure modes are invisible. A timing vulnerability in password comparison looks correct. An insecure random number generator works identically to a secure one for all observable behavior. These issues require deep security expertise to detect, and AI evaluation cannot reliably catch them. The cost of failure is severe. A bug in a security boundary can compromise user data, enable unauthorized access, or create vulnerabilities that persist for years. The downside risk is asymmetric: security bugs cause far more damage than equivalent bugs in non-security code. For security-critical code, write it yourself or have it written by security experts. Then have it audited by different security experts. AI-generated security code should be treated with extreme skepticism, and for core security functionality, it should generally be avoided. This does not mean AI cannot touch anything security-related. Generating a login form that calls secure authentication services is fine. Generating the authentication service itself is not. Learning Situations If your goal is skill development, AI assistance defeats the purpose. The struggle of solving problems yourself is how you build the judgment needed to evaluate AI output. Every time you work through a problem manually, you develop intuition about how code works, what can go wrong, and why certain approaches are better than others. This intuition is exactly what you need to evaluate AI-generated code effectively. The evaluation paradox. ADD requires evaluation skills. Evaluation skills come from experience writing and debugging code. If you always use AI assistance, you do not build the experience that makes you an effective evaluator. You undermine your future capability. For junior developers, this is especially important. Spend significant time coding without AI assistance. Learn what it feels like to debug your own mistakes. Develop the intuition that comes only from struggle. The AI will still be there when you are ready for it, and you will be a better evaluator for having learned without it. For senior developers mentoring juniors, this creates a tension. AI assistance makes juniors more productive immediately, but it may undermine their long-term development. Consider structured programs where juniors spend dedicated time working without AI, building foundational skills before relying on AI assistance. The short-term productivity loss is an investment in long-term capability. For experienced developers, this is why the AI sabbatical concept from Post 1 matters. Skills erode without practice. Periodic time away from AI assistance maintains the evaluation capability that makes ADD work. Learning situations are not just about formal skill development. Any time you are trying to deeply understand a new domain, technology, or codebase, consider working without AI. The slow process of figuring things out yourself builds understanding that AI-assisted speed cannot provide. Highly Stateful and Context-Dependent Systems Some systems have behavior that depends heavily on complex state interactions. Real-time systems, complex state machines, concurrent data structures, distributed systems with subtle consistency requirements. For these systems, specifications become difficult to write comprehensively. The state space is too large to enumerate. The interactions between states are too subtle to describe completely. The timing dependencies are too precise for natural language specification. AI struggles with state complexity. AI models process text sequentially. They do not naturally reason about concurrent state modifications, race conditions, or temporal dependencies. Generated code may handle individual states correctly while failing at state transitions or concurrent access. Evaluation is also harder. How do you evaluate whether a concurrent data structure is correct? You cannot just read the code. You need to reason about interleavings, about memory models, about edge cases in concurrent execution. This reasoning is difficult for humans and not something AI evaluation can assist with. Partial assistance may work. Even in stateful systems, some components are stateless or have simple state. Use ADD for those components while handling complex state management manually. The AI can generate the straightforward parts; you focus on the parts that require careful reasoning about state. For highly stateful systems, traditional development with rigorous analysis, formal methods, or extensive testing may be more appropriate than ADD. The discipline of writing such code manually forces you to reason carefully about state, which is exactly what these systems require. Exploratory and Research Coding Sometimes you are not trying to build something specific. You are exploring possibilities, testing hypotheses, or researching approaches. The goal is understanding, not delivery. ADD's strength is efficiency: going from specification to working code quickly. But exploratory coding is not about efficiency. It is about discovery. The value comes from the journey, not the destination. Specification requires knowing what you want. In exploratory coding, you often do not know what you want until you see what is possible. You cannot write a specification for something you have not yet imagined. Quick experiments may work. For simple "what if" experiments, AI can help. "Generate a quick prototype of X so I can see if it is worth pursuing." But deep exploration often requires the kind of wandering, iterating, and backtracking that AI generation does not support well. When exploring, consider whether AI is accelerating discovery or short-circuiting it. Sometimes the slow path of manual experimentation leads to insights that quick generation would have missed. When Specifications Become as Complex as Implementation ADD's value proposition is that specification is easier than implementation. You describe what you want, the AI implements it, and you evaluate the result. If this cycle is faster than writing the code yourself, ADD provides value. But sometimes specifications become as complex as the implementation they describe. Every edge case must be enumerated. Every constraint must be explicit. Every interaction must be documented. By the time you have written a complete specification, you have done the hard thinking that implementation requires. Signs specification is too complex: The specification is as long as the code would be You keep finding gaps and adding more detail Describing the constraints takes more effort than implementing them You are essentially writing pseudocode in your specification When this happens, just write the code. You have already done the cognitive work. Translating to a specification and back to code adds overhead without adding value. This is not a failure of ADD. It is recognition that some tasks have low specification-to-implementation leverage. The hard part is the thinking, and once you have done the thinking, implementation is straightforward. Hybrid Approaches The boundary between "use ADD" and "don't use ADD" is not always clear. Many projects benefit from hybrid approaches. ADD for some components, traditional for others. In the same project, use ADD for well-specified components and traditional development for security-critical or innovative components. Match the approach to the component's characteristics. ADD for first draft, manual for refinement. Use AI to generate a starting point, then manually refine the parts that need careful attention. The AI accelerates the easy parts; you focus on the hard parts. Traditional for core, ADD for periphery. Build the critical core of the system manually, then use ADD for less critical surrounding functionality. The core gets the attention it needs; the periphery gets the efficiency AI provides. ADD for boilerplate, manual for logic. Some code is genuinely repetitive: CRUD operations, data transfer objects, basic validation. Use ADD for this boilerplate while writing complex business logic manually. The AI handles the tedious parts; you focus on the parts that require judgment. Hybrid approaches require judgment about which approach fits which part of the system. This judgment develops with experience. Start by being conservative about where you apply ADD, then expand as you learn where it works well in your context. Developing Judgment About When to Use ADD The ability to recognize when ADD is not appropriate is itself a skill. It develops with experience. Track your outcomes. When ADD cycles fail repeatedly, ask why. Was this the wrong context for AI assistance? What should you have recognized earlier? Notice the signals. Repeated evaluation failures, specifications that keep growing, generated code that never quite works. These are signals that ADD may not be the right approach for this task. Be honest about results. If ADD is taking longer than manual implementation would have, acknowledge it. The goal is effective development, not using AI for its own sake. Document your boundaries. As you learn where ADD works and does not work in your context, document those boundaries. Share them with your team. Build collective knowledge about when to use which approach. The best ADD practitioners are not those who use ADD for everything. They are those who use ADD for the right things and recognize when to step away. Let's Continue the Conversation What types of tasks have you found unsuitable for AI assistance? Where have you learned to step back to traditional development? How do you make the judgment call between ADD and manual implementation? Share your experience in the comments. Understanding limits is as valuable as understanding capabilities, and your insights help others develop better judgment. --- # Software Companies Are Dead. Just Nobody Told Them. URL: https://www.ivanturkovic.com/2026/02/19/software-companies-are-dead-just-nobody-told-them/ Published: 2026-02-19 Topics: AI-Driven Development, Career & Leadership, Software Engineering Software companies are dead. Just nobody told them. Or so everyone keeps saying. LinkedIn is flooded with Claude Code hype. Cursor is "changing everything." Every founder on your feed is screaming that developers will be redundant by Q2. The revolution is here, they say. Pack your bags. This is now the third consecutive year of this narrative. Three years of breathless predictions. Three years of "this time it is different." And yet, software development companies still exist. Programmers still have jobs. Humans are still an essential part of every serious project. The global software development market hit $824 billion in 2025 and is projected to nearly triple to $2.25 trillion by 2034. The developer population has grown to 47.2 million worldwide, a 50% increase from 2022. Custom software development is expanding at over 20% compound annual growth. These are not the numbers of a dying industry. These are the numbers of an industry accelerating. So what exactly is going on with all this hype? The Pattern We Keep Forgetting If you have been in this industry long enough, the current AI hysteria feels less like a revolution and more like a rerun. The script is always the same. A new technology emerges. It generates genuine excitement. Then the hype machine takes over, and suddenly the technology is not just useful, it is existential. It will not just improve things, it will replace everything that came before. We have seen this show before. In the late 1980s, CASE tools (Computer-Aided Software Engineering) were supposed to automate programming out of existence. Structured methodologies and code generators would make manual coding obsolete. Companies spent billions on these tools. The tools helped with certain tasks. They did not eliminate programmers. In the 1990s, visual programming and fourth-generation languages promised that business users would write their own software. Drag, drop, done. The idea was compelling. The reality was that anything beyond a simple form required a developer who understood what was happening underneath the visual layer. In the 2000s, offshore outsourcing was going to kill domestic software companies. Why pay local rates when you could get the same work done for a fraction of the cost? Some work moved. The industry adapted. Software companies in every market continued to grow because the bottleneck was never just the cost of typing code. In the 2010s, no-code and low-code platforms arrived with the same promise: anyone can build software now. Gartner predicted that 65% of app development would happen on low-code platforms by 2024. These tools found their niche for internal dashboards and simple workflows. They did not replace custom development. The custom software development market is still growing at over 20% annually. Now in the 2020s, AI coding assistants are the latest candidate for the "thing that finally kills developers." The pitch is the same. The outcome will be the same. Not because AI is not powerful, but because the pitch fundamentally misunderstands what software development actually is. The Misconception at the Heart of the Hype Every wave of "developers are dead" hype shares the same underlying assumption: that software development is primarily about writing code. If you believe that, then any tool that writes code faster, or writes it for you, logically threatens the entire profession. But software development has never been primarily about writing code. Writing code is the most visible part of the process. It is also one of the smallest. The actual work of software development is understanding what needs to be built and why. It is translating vague business requirements into precise technical specifications. It is making architectural decisions that will determine whether a system scales or collapses under load. It is debugging not just code but the assumptions behind the code. It is navigating trade-offs between speed, quality, security, and cost. It is communicating with stakeholders who do not know what they want until they see what they do not want. None of this is about typing syntax faster. When someone demonstrates an AI tool building a todo app in 30 seconds, the audience sees magic. What they do not see is that the todo app has no authentication, no authorization, no error handling for edge cases, no audit logging, no accessibility compliance, no data migration strategy, no monitoring, no deployment pipeline, and no plan for what happens when the requirements change next week. Which they will. The demo is impressive. The demo is also the easiest 2% of the job. What AI Actually Does (and Does Well) I want to be clear about something: I am not dismissing AI tools. I use them every single day. They are genuinely incredible for certain tasks, and any developer who refuses to use them out of pride or fear is making a mistake. AI coding assistants are excellent at generating boilerplate code. They handle repetitive patterns faster than any human could. They are useful for exploring unfamiliar APIs, generating test scaffolding, drafting documentation, and suggesting approaches to well-defined problems. They save time. They reduce tedium. They let developers focus on the parts of the work that actually require human judgment. That is the key distinction. AI tools are force multipliers for skilled developers, not replacements for them. A skilled developer with AI tools is dramatically more productive than the same developer without them. But an unskilled person with AI tools is not a developer. They are someone who can generate code they cannot evaluate, debug, or maintain. There is a meaningful difference between producing code and building software. The data supports this. Recent analysis found that 45% of AI-generated code contains vulnerabilities from the OWASP top 10 security risks. In Java specifically, the security failure rate for AI-generated code exceeds 72%. Experienced engineers have reported being slower when using AI tools for complex tasks because the time spent prompting, waiting, and reviewing outweighed the gains. AI-generated pull requests show nearly double the issues compared to human-written code. These are not reasons to avoid AI tools. They are reasons to understand that AI tools need skilled humans operating them. A chainsaw is more productive than a hand saw, but handing a chainsaw to someone who has never felled a tree does not make them a lumberjack. The Real Numbers Tell a Different Story If AI were genuinely replacing software companies and developers, we would see it in the numbers. We would see declining market size, shrinking developer populations, and reduced demand for custom software. We see the opposite across every metric. The global software development market is valued at approximately $524 billion in 2025, with projections to exceed $1 trillion by 2032 at a compound annual growth rate of over 10%. Worldwide IT spending will reach $5.74 trillion this year, with software spending alone growing 14%. The custom software development market is on track to grow from $43 billion in 2024 to $146 billion by 2030. The developer population tells the same story. The global count has reached 47.2 million developers, with the professional segment growing 70% since 2022. The US Bureau of Labor Statistics projects 17% compound annual employment growth for software developers, a rate described as "much faster than average." South Asia has nearly doubled its developer population from 4 million to 7.5 million in three years. These numbers do not describe an industry being disrupted into extinction. They describe an industry experiencing sustained, accelerating growth, powered in part by the very AI tools that are supposedly killing it. Why Each Wave Creates More Demand, Not Less There is a paradox that the "developers are dead" crowd consistently overlooks. Every technology that was supposed to eliminate developers has, in the long run, created more demand for them. This is not a coincidence. It is a structural feature of how technology markets work. When tools become more powerful, the ambition of what people want to build increases proportionally. CASE tools did not eliminate developers; they enabled more complex enterprise systems that required more developers. Visual programming did not replace coding; it created a new layer of abstraction that still needed programmers underneath. No-code platforms did not end custom development; they showed businesses what was possible, which generated demand for more sophisticated custom solutions that the platforms could not deliver. AI tools are following the same trajectory. By making it faster to build prototypes and MVPs, they are expanding the market for software. More ideas can be tested. More businesses can explore digital products. More prototypes need to be turned into production systems. And turning a prototype into a production system still requires people who understand architecture, security, scalability, compliance, and the hundred other concerns that separate a demo from a product. The spreadsheet is perhaps the best historical analogy. When spreadsheets arrived, some predicted they would eliminate the need for accountants and financial analysts. Instead, spreadsheets democratized financial modeling, which dramatically increased the demand for people who could build, validate, and maintain complex models. The tool did not replace the expertise. It amplified it. AI is doing the same thing to software development right now. What Is Actually Changing Saying that software companies are not dead does not mean nothing is changing. Quite a lot is changing, and developers and companies that ignore these shifts will struggle. The changes are just different from what the hype suggests. First, the value of implementation speed is dropping. When AI can generate functional code in seconds, the ability to write code quickly is worth less than it used to be. The skills that are increasing in value are the ones AI cannot replicate: understanding what to build, defining clear specifications, designing systems that can evolve, and making judgment calls about trade-offs that involve business context, user behavior, and technical constraints simultaneously. Second, the role of the developer is shifting toward orchestration. The best developers I know today spend less time writing code from scratch and more time directing AI tools, reviewing their output, integrating components, and ensuring that the overall system holds together. This is not a diminishment of the role. It is an elevation. The developer is becoming more of a system architect and less of a typist. That is a good thing. Third, the bar for entry-level developers is changing. With AI handling much of the boilerplate that juniors used to write, the pathway for new developers looks different. There is a real risk that companies stop hiring juniors because AI handles "junior tasks," which would be a serious long-term mistake. Today's juniors are tomorrow's senior engineers and architects. The pipeline matters, and industry leaders need to think carefully about maintaining it. Fourth, clarity is becoming the most valuable skill in software development. AI does not struggle with hard problems. AI struggles with vague problems. The developers and companies that can translate ambiguous business needs into precise specifications will extract the most value from AI tools. Those who cannot will produce code that technically runs but does not solve the right problem. The People Driving the Hype It is worth asking who benefits from the "software companies are dead" narrative, because the answer explains a lot about why it persists despite the evidence. AI tool companies benefit from it. If developers are about to be replaced, then every company urgently needs their product. Fear is the most effective sales pitch in technology. "Your competitors are already using this" is the modern equivalent of "nobody ever got fired for buying IBM." Founders raising capital benefit from it. Telling investors that AI will reduce your engineering headcount by 70% makes your unit economics look spectacular on a pitch deck. Whether it materializes is a problem for future quarters. Content creators and influencers benefit from it. "AI will kill developers" generates more engagement than "AI is a useful productivity tool." Nuance does not go viral. Apocalyptic predictions do. Management consultants benefit from it. If AI can replace your engineering team, you need a consultant to help you navigate the transition. The transition that never quite arrives, but the consulting engagement continues regardless. The people who do not benefit from this narrative are the ones building real products for real users. They know the truth, which is that AI makes them faster but does not make them unnecessary. They are too busy shipping to post about it on LinkedIn. What Smart Software Companies Are Actually Doing While the commentators predict their demise, the software companies that are thriving have a straightforward approach. They are integrating AI deeply into their workflows. Not as a replacement for their teams, but as infrastructure that makes their teams more capable. They are using AI to accelerate code reviews, generate test suites, draft initial implementations that experienced developers then refine, and automate deployment pipelines. They are building internal tools that combine AI capabilities with domain expertise. They are training their teams to be effective at working with AI, not to be replaced by it. The result is not fewer developers. It is the same number of developers (or more) producing significantly more value. Projects that used to take six months are done in three. Features that required a full sprint are delivered in days. Teams that could handle three clients can now handle five. This is what actually happens when a new tool is powerful: the people who master it become more productive and more valuable. The tool does not eliminate the profession. It raises the ceiling on what the profession can accomplish. The Uncomfortable Truth for the "Dead" Crowd Here is what the "software companies are dead" crowd does not want to confront: the 95% failure rate of enterprise generative AI pilots to deliver measurable ROI. The finding that two-thirds of tech leaders who integrated AI have not saved a single human headcount. The analysis showing that AI-generated code has created a wave of technical debt, with one study estimating it would take 61 billion work days to address the world's current code quality deficit. The reports of a 4x surge in code cloning, producing what engineers call the "slop layer" of code that works but nobody understands. None of this means AI is failing. It means AI is not doing what the hype promised it would do. It is doing what powerful tools have always done: making skilled people more effective while creating new categories of problems that require skilled people to solve. The companies that tried to replace their developers with AI are now hiring developers to fix what the AI produced. The companies that used AI to empower their developers are shipping better products faster. The difference between these two outcomes is not the technology. It is the understanding of what software development actually requires. Software Companies Are Not Dead. They Are Evolving. The tools have changed. The fundamentals have not. Software development still requires understanding the problem before writing the solution. It still requires architectural thinking. It still requires someone to make judgment calls about trade-offs that no model can make because the relevant context lives in conversations, relationships, business constraints, and hard-won experience, not in training data. The developers who are thriving in 2026 are not the ones who write the fastest code. They are the ones who define the clearest specifications, make the best architectural decisions, and know how to leverage AI as a tool rather than surrendering to it as a replacement. They are orchestrators, not typists. Software companies are not dead. Not even close. They are adapting, as they always have. The ones who understand that AI is an amplifier will grow. The ones who believed the hype that AI is a replacement will learn an expensive lesson. The rest of us will keep building. That is what we have always done. Final Words If this resonated with you, or even if you disagree, I would love to hear your take. The best conversations happen when people with real experience push back, share their own observations, and add nuance to the discussion. You can follow me on LinkedIn for regular thoughts on software engineering, AI, and the realities of building technology over the long term. I also write on Threads for shorter, sharper takes. If you want to discuss this topic further, collaborate on something, or just say hello, feel free to reach out. I respond to everything. What do you think? Are software companies really dying, or are we just watching the same cycle repeat itself with better special effects? Drop me a message. I am genuinely curious what you are seeing from where you sit. --- # ADD Meets TDD, BDD, and Agile: Combining Methodologies URL: https://www.ivanturkovic.com/2026/02/18/add-meets-tdd-bdd-agile/ Published: 2026-02-18 Topics: AI-Driven Development ADD is not a replacement for existing development methodologies. It is a complement to them. Teams already practicing Test-Driven Development, Behavior-Driven Development, or Agile workflows have a head start with ADD because these methodologies share underlying principles with AI-Driven Development. This post explores how ADD integrates with each methodology and how the combinations create practices stronger than any single approach. ADD and Test-Driven Development Test-Driven Development follows a simple cycle: write a failing test, write code to make it pass, refactor. Red, green, refactor. This cycle ensures that code is always testable and that tests document expected behavior. ADD and TDD are natural partners. Tests as specification components. In pure TDD, tests serve as the specification. In ADD, tests become part of the specification you provide to the AI. "Here are the tests. Generate implementation that passes them." The tests define behavior precisely, and the AI generates code to satisfy them. This is the Test-First Pattern from Post 8, but it is also just TDD practiced with AI assistance. The discipline remains: write the test first, then generate implementation. TDD within the Evaluate phase. After generating code, evaluation includes running tests. But ADD evaluation goes beyond test passage. Code that passes tests might still have fitness problems, security issues, or maintainability concerns. TDD provides a strong foundation for the Correctness dimension of evaluation while ADD's broader evaluation covers dimensions that TDD does not address. Red-Green-Refactor meets Specify-Generate-Evaluate-Integrate. The cycles nest. Within a single ADD cycle, you might have multiple TDD cycles. Write a test (red), generate implementation (green), evaluate and possibly regenerate (refactor). Then move to the next test. The ADD cycle encompasses the TDD cycles, adding specification depth before and evaluation breadth after. Faster cycle times. With AI generation, the "green" phase of TDD accelerates. You do not write the implementation line by line; you generate it. This allows more iterations in the same time. More iterations means more opportunities to refine both tests and implementation. Better test coverage. ADD can help generate additional tests after initial implementation. Once you have code that passes your core tests, use ADD to generate edge case tests, error condition tests, and integration tests. The AI can identify test cases you might not have considered. Test generation from specifications. You can also use ADD to generate tests from specifications before writing any implementation. This is TDD with AI assistance at the test-writing stage. Specify the behavior you want, generate tests that verify that behavior, then generate implementation that passes the tests. The AI helps with both halves of the TDD cycle. The discipline transfers. Developers practiced in TDD already have the discipline that ADD requires: thinking about behavior before implementation, writing precise specifications (as tests), and evaluating results rigorously. If you practice TDD, you already understand why specification comes first. ADD and Behavior-Driven Development Behavior-Driven Development extends TDD with a focus on behavior specification that stakeholders can read and validate. BDD uses structured formats like Gherkin (Given-When-Then) to describe behavior in natural language that maps to executable tests. ADD and BDD align closely because BDD scenarios are already excellent specifications. Gherkin scenarios as ADD specifications. A well-written Gherkin scenario describes context (Given), action (When), and expected outcome (Then). This is precisely what an ADD specification needs: the situation, the behavior, and the result. If you write BDD scenarios, you are already practicing the Specify phase of ADD. Consider a scenario: gherkin Given a user with valid credentials When they submit the login form Then they should be redirected to the dashboard And a session should be created with their user ID This scenario is a specification. It defines inputs, actions, and expected outputs. The AI can generate implementation that satisfies this behavior. Given-When-Then as generation guidance. The structure of BDD scenarios helps the AI understand what you need. "Given" establishes context and preconditions. "When" identifies the action to implement. "Then" defines success criteria. This structure maps naturally to function signatures, setup code, and assertions. Stakeholder-readable specifications. BDD scenarios are designed to be readable by non-technical stakeholders. This means your ADD specifications can be reviewed by product managers, designers, or domain experts before generation. Early review catches specification errors before they become implementation errors. Scenario coverage drives evaluation. BDD provides a natural checklist for evaluation: does the implementation satisfy all scenarios? But as with TDD, ADD evaluation goes beyond scenario passage. Scenarios describe happy paths and key error cases, but they rarely cover all edge cases, security considerations, or performance requirements. ADD evaluation supplements scenario coverage. Generating scenarios. You can also use ADD in the opposite direction: generate BDD scenarios from requirements or existing code. This helps when documenting legacy systems or when expanding scenario coverage for existing features. Specify the functionality and ask for Gherkin scenarios that would verify it. Then evaluate whether the generated scenarios capture the behavior that matters. Living documentation. BDD scenarios serve as living documentation of system behavior. When you use BDD scenarios as ADD specifications and then generate implementation, the scenarios document what the generated code does. The documentation stays synchronized with the implementation because both derive from the same specification. ADD in Agile Workflows Agile is not a single methodology but a family of approaches sharing principles: iterative development, customer collaboration, responding to change, working software over documentation. ADD fits naturally within Agile workflows. Story refinement as specification activity. In Agile, stories are refined from high-level descriptions ("As a user, I want to reset my password") into detailed acceptance criteria. This refinement conversation is the specification activity of ADD. The output of refinement should be detailed enough to serve as an ADD specification. Consider reframing refinement slightly: instead of "what does the developer need to know to implement this?" ask "what does the specification need to contain for generation to succeed?" The questions are similar, but the second prompts more precision about inputs, outputs, constraints, and edge cases. Estimation implications. AI generation changes the effort profile of stories. Implementation time decreases, but specification and evaluation time may increase or at least become proportionally more significant. When estimating stories, account for the full ADD cycle, not just generation. A story that generates quickly but requires extensive evaluation is not a small story. Some teams find that story points remain similar overall: faster implementation offset by more thorough specification and evaluation. Other teams find that ADD reduces total effort significantly for certain story types. Calibrate your estimates based on experience with ADD in your context. Sprint integration. ADD cycles are typically faster than traditional implementation cycles, which creates opportunities within sprints. A story that would have taken three days to implement might generate in an hour, leaving time for more thorough evaluation, additional testing, or taking on additional stories. But do not let the speed of generation pressure you into skipping evaluation. The goal is better outcomes, not more output. Retrospectives should cover ADD. Include ADD practices in your retrospectives. What specifications led to successful generations? What evaluation caught issues that should have been specified? What did we learn about when ADD works well and when it struggles? Retrospectives are the Agile mechanism for process improvement, and ADD is part of your process. Pairing and mobbing with AI. Pair programming and mob programming remain valuable with ADD. Two developers can collaborate on specifications, discuss evaluation findings, and make integration decisions together. The AI accelerates the generation phase, but human collaboration remains essential for specification and evaluation. Some teams find that pairing shifts toward specification and evaluation, with generation becoming a brief interlude. Demos and stakeholder feedback. Agile emphasizes frequent demos and stakeholder feedback. ADD accelerates the cycle from story to working software, which means more frequent opportunities for feedback. You can demonstrate generated features sooner, get feedback sooner, and incorporate that feedback into the next ADD cycle. The rapid iteration that Agile values becomes even more achievable with AI-assisted generation. Handling change. Agile embraces change, even late in development. ADD makes responding to change easier because regenerating code from a modified specification is often faster than manually refactoring existing code. When requirements change, update the specification and regenerate. This does not eliminate the cost of change, but it reduces the implementation portion of that cost. ADD in Continuous Delivery Pipelines Continuous delivery emphasizes frequent, reliable deployment through automated pipelines. ADD integrates with CD practices naturally. Automated evaluation gates. Your CD pipeline already includes automated checks: tests, linting, security scanning. These checks apply to AI-generated code just as they apply to human-written code. Treat the pipeline as part of the Evaluate phase. Issues caught by the pipeline are evaluation findings. Consistent code quality. ADD with good specifications and evaluation tends to produce consistent code quality. This consistency makes CD more reliable. Fewer surprises in code quality means fewer pipeline failures, fewer rollbacks, and more confidence in deployments. Specification versioning. Consider versioning your specifications alongside your code. If a deployed feature has issues, you can trace back to the specification that produced it. This traceability helps with debugging, with process improvement, and with understanding how specifications should evolve. Feature flags and ADD. Feature flags allow deploying code without exposing it to users. This creates space for ADD experiments: generate a feature, deploy it behind a flag, evaluate it in production context without user exposure, then either enable or remove it. Feature flags reduce the risk of ADD-generated code reaching users before thorough evaluation. Choosing Combinations Not every team needs every methodology. The right combination depends on your context. If you practice TDD, adding ADD is straightforward. Tests become specification components. Generation accelerates the implementation phase. Evaluation extends beyond test passage. The combination is natural and reinforcing. If you practice BDD, your scenarios are already specifications. ADD completes the loop from specification to implementation. The combination is particularly powerful because BDD specifications are precise enough for reliable generation. If you practice Agile without TDD or BDD, adding ADD may require more change. Story refinement needs to produce specifications detailed enough for generation. Evaluation needs explicit practices since you may not have test-driven habits. But ADD can also be the catalyst for adopting more disciplined specification practices. If you practice none of these, start with ADD and let it pull you toward better practices. ADD's requirement for clear specifications encourages TDD-like thinking. ADD's evaluation phase encourages BDD-like scenario coverage. ADD's iterative nature aligns with Agile principles. Sometimes adopting one discipline creates pressure for others. The Methodologies Reinforce Each Other Each methodology strengthens the others. ADD makes TDD easier because you can generate tests from specifications, not just implementation from tests. TDD makes ADD safer because tests verify generated code and catch regressions. ADD makes BDD more powerful because scenarios become directly executable specifications. BDD makes ADD more accessible because scenarios are already clear specifications. Agile makes ADD sustainable because iterative delivery allows continuous improvement of ADD practices. ADD makes Agile faster because generation accelerates implementation within sprints. The combinations are not just compatible; they are synergistic. Teams practicing multiple methodologies with ADD report that each becomes more valuable in combination than it was alone. Let's Continue the Conversation Does your team practice TDD, BDD, or Agile? How has AI assistance changed or reinforced those practices? What challenges have you encountered combining ADD with existing methodologies? Share your experience in the comments. Different teams find different combinations work best, and your insights can help others find their path. --- # ADD in Context: Greenfield, Legacy, Refactoring, and Testing URL: https://www.ivanturkovic.com/2026/02/16/add-in-context-greenfield-legacy-refactoring/ Published: 2026-02-16 Topics: AI-Driven Development, Software Engineering The ADD cycle is consistent across contexts: Specify, Generate, Evaluate, Integrate. But how you apply each phase changes depending on what you are building and where you are building it. Greenfield projects offer freedom that legacy codebases do not. Refactoring has constraints that new feature development lacks. Test generation inverts the typical flow. This post covers how ADD adapts to different development contexts. The cycle remains the same, but the emphasis shifts. ADD for Greenfield Projects Greenfield projects offer maximum flexibility. There is no existing code to constrain your choices, no legacy patterns to follow, no technical debt to navigate. This freedom is valuable, but it comes with responsibility. Establish patterns early. The first components you build become exemplars for everything that follows. If you generate a sloppy API endpoint early, that sloppiness propagates when you use it as an exemplar for subsequent endpoints. Invest extra time in the Specify and Evaluate phases for your first few generations. Get the patterns right, and the AI will replicate them. Get them wrong, and you will replicate the problems. Build your context library as you go. In greenfield development, you do not have existing code to provide context. But after your first few ADD cycles, you do. Extract the patterns that work. Document the conventions you establish. Build the context library that will guide future generations. The earlier you start, the more consistent your codebase becomes. Specify architectural decisions explicitly. The AI does not know your architectural intentions unless you state them. If you want clean architecture with clear layer separation, specify that. If you want a particular error handling approach, specify that. If you want specific naming conventions, specify those. In legacy codebases, the existing code implies these decisions. In greenfield, you must be explicit. Resist the temptation to move fast and skip evaluation. Greenfield projects often have aggressive timelines. The temptation is to generate rapidly and defer careful evaluation. This is a mistake. Technical debt accumulates faster in greenfield projects because there are no existing patterns to guide the AI toward consistency. Thorough evaluation early prevents larger problems later. Plan for iteration. Your first specifications will be imperfect. Your early code will need revision as you learn more about the domain and the system. Build with the expectation that you will revisit and refine. ADD cycles are not one-time events; they are iterative. The code from cycle one informs the specifications for cycle two. Define boundaries early. In greenfield projects, module boundaries, API contracts, and data schemas are all undetermined. Define these boundaries in your specifications before generating implementations. Clear boundaries make each ADD cycle more focused and reduce the coupling between generated components. Changing boundaries later is expensive; getting them right early pays dividends throughout the project. ADD for Legacy Codebases Legacy codebases present the opposite challenge. You have context, perhaps too much of it. Existing patterns may be inconsistent, outdated, or undocumented. The code that exists constrains the code you can add. Context is everything. Your specifications must account for how the existing system works, not just what you want the new code to do. Include existing interfaces, data structures, error handling patterns, and naming conventions in your specifications. The more context you provide, the better the AI can generate code that fits. Use existing code as exemplars heavily. The Exemplar Pattern is particularly valuable in legacy contexts. Instead of describing the patterns you want, show them. Find the best existing examples of similar functionality and include them in your specification. The AI learns from concrete code, not abstract descriptions of conventions. Evaluate fitness rigorously. In legacy codebases, the Fitness dimension of evaluation becomes paramount. Code that is correct in isolation may be wrong in context. Does the generated code follow the same patterns as surrounding code? Does it use the same error handling approach? Does it interact correctly with existing components? Fitness evaluation requires deep knowledge of the existing system. Accept imperfection. Legacy codebases often have inconsistent patterns. You may need to generate code that follows a pattern you would not choose for new development, simply because that is what the existing system uses. Consistency with the existing system often matters more than ideal patterns. Document this as intentional technical debt when it occurs. Specify constraints from existing patterns. Use the Constraint Pattern to prevent the AI from introducing approaches that conflict with the existing system. "Do not use async/await; this module uses callbacks throughout." "Do not introduce new exception types; use the existing error handling framework." These constraints keep generated code compatible with legacy patterns. Plan for incremental improvement. You rarely rewrite legacy systems all at once. ADD in legacy contexts often means small improvements over time. Each cycle makes the system slightly better: clearer code, better tests, improved documentation. The cumulative effect of many small improvements can be substantial. Map the existing system first. Before applying ADD to a legacy codebase, invest time understanding the existing system. What patterns does it use? What are its conventions? Where are the inconsistencies? This mapping exercise produces the context you need for effective specifications and the knowledge you need for accurate evaluation. ADD for Bug Fixes Bug fixes are a specific context where ADD applies differently. The specification comes not from new requirements but from the gap between expected and actual behavior. Reproduction is specification. The bug report describes what goes wrong. Your specification should include the reproduction steps, the expected behavior, and the actual behavior. The more precisely you can describe the conditions under which the bug occurs, the better the AI can generate a fix. Include the failing test. If you have a test that demonstrates the bug, include it in your specification. "This test should pass but currently fails. Generate a fix that makes the test pass without breaking other tests." The test serves as an executable specification that the AI can target. Evaluate for regression. When fixing bugs, the primary evaluation concern is regression. Does the fix break anything else? Does it handle the specific case while preserving correct behavior for other cases? Run the full test suite, not just the test for the bug being fixed. Trace the root cause. The AI might generate a fix that addresses symptoms rather than the root cause. Evaluate whether the fix addresses the underlying problem or just patches over it. A symptom fix may resolve the immediate issue but leave the system vulnerable to similar bugs. Document the fix context. Bug fixes benefit from documentation: what was the bug, why did it occur, how does the fix address it? This context helps future developers understand the code and prevents reintroduction of the same bug. ADD for Refactoring Refactoring changes code structure without changing behavior. This constraint fundamentally shapes how ADD applies. Behavior preservation is the primary constraint. Your specification must be explicit: the refactored code must behave identically to the original. Include the original code, describe the structural changes you want, and emphasize that behavior must not change. "Refactor this function to use early returns instead of nested conditionals. The function must produce identical outputs for all inputs." Tests are both specification and safety net. Existing tests define the behavior that must be preserved. If the tests pass before refactoring and pass after, behavior is preserved. If you lack tests, consider generating them first (see next section) before refactoring. Refactoring without tests is risky, whether the refactoring is human or AI-driven. Specify the structural goals clearly. Why are you refactoring? To improve readability? To enable future extension? To reduce duplication? To improve performance? The AI needs to understand the goal to make appropriate structural choices. "Refactor to separate data access from business logic" is clearer than "refactor to improve the code." Evaluate both behavior and structure. Evaluation has two dimensions: does the behavior remain identical, and does the structure achieve the refactoring goal? Code that preserves behavior but does not improve structure is not a successful refactoring. Code that improves structure but changes behavior is a bug. Proceed incrementally. Large refactorings are risky. Break them into smaller steps, each with its own ADD cycle. Refactor one function, verify behavior, integrate. Then refactor the next. The incremental approach limits the blast radius if something goes wrong and makes evaluation more tractable. ADD for Test Generation Test generation inverts the typical ADD flow. Instead of specifying behavior and generating implementation, you specify implementation and generate tests. Code under test is the specification. When generating tests, the existing code defines what behavior you are testing. Include the code in your specification. Describe what the code is supposed to do. Identify the critical behaviors that tests should verify. Specify coverage goals. What should the tests cover? Happy paths, edge cases, error conditions, boundary values? Be explicit about coverage expectations. "Generate tests that cover all branches" or "Generate tests for the error handling paths" gives the AI clear direction. Specify testing patterns. What testing framework should be used? What assertion style? What test organization? Include examples of existing tests as exemplars. The generated tests should fit with your existing test suite. Evaluate test quality, not just test existence. Generated tests can be superficial: they exist, they pass, but they do not verify meaningful behavior. Evaluate whether tests actually catch the bugs they should catch. Consider mutation testing: if you introduce bugs into the code, do the tests fail? Tests that pass regardless of bugs are not valuable tests. Watch for tautological tests. A common AI failure mode in test generation is creating tests that verify the code does what the code does, rather than what the code should do. "Assert that add(2, 3) returns 5" is a meaningful test. "Assert that process(x) returns process(x)" is tautological. Evaluate whether tests encode expected behavior or merely mirror implementation. Specify negative test cases. AI tends toward positive test cases: inputs that work, paths that succeed. Explicitly specify negative test cases in your specification: invalid inputs, error conditions, boundary violations. "Generate tests that verify the function rejects invalid email formats" is more likely to produce valuable negative tests than a generic "generate tests" request. Generate tests before refactoring. If you plan to refactor code that lacks tests, generate tests first. The tests capture current behavior. Then refactor. If tests pass after refactoring, behavior is preserved. This test-first-refactor-second approach reduces refactoring risk. ADD for Documentation Documentation is another context where ADD inverts: code is the input, human-readable text is the output. Code is the specification. When generating documentation, the code defines what you are documenting. Include the relevant code in your specification. Describe what kind of documentation you need: API reference, tutorial, architectural overview, inline comments. Specify audience and purpose. Who will read this documentation? New developers onboarding to the project? API consumers integrating with your service? Operations teams deploying and monitoring? The audience shapes the content, level of detail, and assumed knowledge. Evaluate accuracy above all. AI-generated documentation can be fluent, well-structured, and completely wrong. Evaluation must verify that the documentation accurately describes what the code does. This requires reading both the documentation and the code and confirming they match. Do not trust that the AI understood the code correctly. Watch for confident hallucination. AI models can generate documentation that describes features the code does not have or behaviors that do not occur. This is particularly common when the code is complex or when the AI fills in gaps based on what similar code might do. Verify every claim in the documentation against the actual implementation. Update documentation atomically with code. When you generate new code, generate or update documentation in the same cycle. Documentation that lags behind code becomes misleading. Integrate documentation updates with code integration. ADD for Code Review ADD can also inform how you review code, whether human-written or AI-generated. Apply evaluation dimensions consistently. The five evaluation dimensions from Post 10 (correctness, fitness, security, performance, maintainability) apply to all code. Use them as a framework for reviewing human-written code, not just AI-generated code. Consistency in review standards improves overall code quality. Use AI to assist review. You can use ADD cycles to help review complex code. Specify the code under review and ask the AI to identify potential issues, explain complex logic, or suggest improvements. The AI's output is not authoritative, but it can surface concerns you might miss. Review AI-generated code with extra attention to AI failure modes. When reviewing code you know was AI-generated, pay particular attention to the common AI failure modes covered in Post 10: plausible but incorrect logic, training data bias, hidden assumptions, confident incorrectness, copy-paste drift. These are more likely in AI-generated code than human-written code. Share review findings to improve the cycle. When code review catches issues, feed that learning back into the ADD cycle. If review consistently catches certain types of problems, those problems should be addressed earlier through better specifications, more explicit constraints, or updated evaluation checklists. Consider AI-assisted review. For complex pull requests, you can use ADD cycles to help with review. Generate summaries of changes, identify potential issues, or create review checklists specific to the code being reviewed. AI assistance in code review complements human judgment rather than replacing it. Document review patterns. Over time, you will notice patterns in what code review catches for AI-generated code versus human-written code. Document these patterns. They inform both your evaluation practices and your specification practices. If reviews consistently catch the same type of issue in AI-generated code, your specifications should prevent that issue from occurring. Let's Continue the Conversation Which context do you work in most often? How has AI assistance worked differently in greenfield versus legacy environments? Have you used AI for refactoring or test generation? What worked well, and what challenges did you encounter? Share your experience in the comments. Different contexts surface different challenges, and collective experience helps everyone apply ADD more effectively. --- # WebMCP Is Coming: How AI Agents Will Reshape the Web URL: https://www.ivanturkovic.com/2026/02/15/webmcp-is-coming-how-ai-agents-will-reshape-the-web/ Published: 2026-02-15 Topics: AI On February 10, 2026, the Chrome team quietly published a blog post announcing something called WebMCP. It was framed as an "early preview," tucked behind a feature flag in Chrome 146. If you blinked, you missed it. But if you understand what it actually does, you realize it might be the most consequential browser feature since the introduction of JavaScript itself. WebMCP stands for Web Model Context Protocol. It is a proposed W3C web standard, co-authored by engineers from Microsoft and Google, that lets websites expose structured tools directly to AI agents. Instead of agents scraping your DOM, clicking buttons, or guessing how your forms work, your site explicitly tells them: here is what I can do, here is how to invoke it, and here is what I need from you. That might sound incremental. It is not. WebMCP is the first serious attempt to build a parallel interface layer for the web, one designed not for humans, but for machines. And if you are a developer, a CTO, or anyone who builds products that live on the internet, the implications of this shift deserve your full attention. Two Internets Are Emerging For thirty years, the web has had one audience: people. Every design decision, every UX pattern, every pixel on every page was optimized for human eyes and human hands. Navigation menus exist because humans need visual wayfinding. Buttons are styled to look clickable because humans need affordances. Forms have labels and placeholders because humans need instructions. AI agents do not need any of that. When an agent wants to book a flight, it does not need to "see" a calendar widget. It needs to know: what function do I call, what parameters does it expect, and what does it return? The visual interface is not just unnecessary for machines. It is an obstacle. It forces agents to simulate human behavior, taking screenshots, parsing DOM trees, guessing which element is a submit button, hoping they got the date format right. This is how agentic browsing works today. Chrome's auto-browse feature uses Gemini to "see" web pages and interact with them the way a human would. Click this. Scroll there. Look for the checkout link. It works, sort of. But it is slow, fragile, and expensive. A simple search can consume thousands of tokens just to process screenshots and parse page structure. And every time a website redesigns its UI, the agent breaks because the buttons moved. WebMCP proposes a fundamentally different approach. Instead of making agents pretend to be humans, you give them their own interface. Your website publishes a structured menu of tools, a machine-readable list of actions with defined inputs, outputs, and behaviors. The agent reads the menu, calls the function, gets structured data back. No screenshots. No DOM parsing. No guessing. What this means in practice is that the web is forking. One layer remains for humans: the visual, interactive, beautifully designed experience we know. A second layer emerges for machines: structured, schema-driven, and optimized for programmatic consumption. WebMCP is the specification for that second layer. How WebMCP Actually Works WebMCP introduces a new browser API called navigator.modelContext. Through this API, websites can register tools that AI agents can discover and invoke directly. The specification proposes two complementary approaches, and you can use both on the same page. The Declarative API: Agent-Ready Forms With Two Attributes The simplest path to WebMCP is the declarative API. If your website already has HTML forms (and nearly every website does), you can make them agent-readable by adding just two attributes: toolname and tooldescription. No JavaScript required. html
The browser automatically generates a tool schema from the form's input field names, types, and validation rules. When an agent invokes the tool, the browser pre-fills the form and, by default, waits for the user to confirm before submitting. You can add toolautosubmit="true" to allow automatic submission without human confirmation, though the security implications of that choice deserve careful thought. This is remarkably low-friction. If you have already invested in semantic HTML and accessible forms, you are already halfway to being WebMCP-ready. The investment in proper HTML structure, something that good developers have been preaching for decades, suddenly pays a brand new dividend. The Imperative API: Full Programmatic Control For complex, stateful interactions that go beyond what HTML forms can express, WebMCP provides an imperative JavaScript API. You register tools programmatically with navigator.modelContext.registerTool(), define a name, description, JSON input schema, and an execute callback. javascript navigator.modelContext.registerTool({ name: 'add_to_cart', description: 'Add a product to the shopping cart', inputSchema: { type: 'object', properties: { productId: { type: 'string', description: 'The product identifier' }, quantity: { type: 'number', description: 'Number of items to add' } }, required: ['productId'] }, async execute(args) { const result = await cartService.addItem( args.productId, args.quantity || 1 ); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } }); Notice something important here. The execute function is just regular JavaScript. You are not building a separate API or integration layer. You are exposing what your application already does. Your existing business logic, your existing authentication context, your existing session state. WebMCP tools run in the browser tab, inheriting everything the user's session already has. This is fundamentally different from traditional MCP (Model Context Protocol), which requires a backend server, usually in Python or Node.js, with separate authentication and server-to-server communication. WebMCP moves the entire integration surface to the client side. Your website becomes the MCP server. What This Means for Different Types of Applications The use cases become clearer when you think about specific application categories and how they would expose tools. An e-commerce site could expose a search_products tool with structured filters for category, price range, size, and color. An agent helping a user find "waterproof hiking boots under $150 in size 10" would invoke the filter tool with exact parameters and receive structured product data: IDs, prices, availability. When the user changes their mind about the color, the agent modifies one parameter and calls the tool again instantly. No page reload. No re-scraping. Deterministic results. A travel booking platform could expose tools for flight search, hotel filtering, and itinerary building. Instead of the agent navigating through a multi-step booking wizard, clicking through calendar pickers and dropdown menus, it would call search_flights({ origin: "NYC", destination: "LAX", date: "2026-04-15" }) and receive structured results. Google has already published a live demo of exactly this scenario at their travel booking reference implementation. A SaaS application could expose a create_project tool, letting agents work through your API on your terms rather than trying to navigate your dashboard UI. A news publisher could expose search_archive and subscribe tools, ensuring that agents discover content and hit the paywall in a way the publisher controls. A customer support platform could expose create_ticket with structured fields for category, priority, and description, enabling agents to file detailed support requests automatically. The common thread is control. WebMCP does not ask you to open your application up to arbitrary scraping. It asks you to define exactly what is available, what parameters are required, and how the interaction should work. You decide what is exposed and what remains behind gates. The SEO Parallel: Why This Feels Familiar If you have been in the industry long enough, WebMCP will trigger a strong sense of déjà vu. The parallels to the early days of search engine optimization are striking, and they are worth examining because they tell us something about where this is heading. In the mid-1990s, search engine crawlers showed up and tried to make sense of web pages designed for humans. They parsed HTML, guessed at content relevance, and ranked pages based on heuristics. Then came robots.txt, sitemaps, structured data, and meta tags. Websites started publishing machine-readable signals alongside their human-readable content. A new discipline emerged. The sites that adopted these standards early captured disproportionate traffic. The ones that ignored them became invisible. WebMCP is the beginning of the same paradigm shift, but for AI agents instead of search crawlers. And the specification's own documentation acknowledges this directly. Tool discoverability is currently an unsolved problem. There is no way for agents to know which sites have tools without visiting them first. The docs hint at future manifest-based discovery, something like a .well-known/webmcp file, so agents can find tools before opening tabs. When that discovery layer emerges, optimizing for it will become a discipline in itself. Tool descriptions will function like meta descriptions. The quality of your tool's name, description, and schema will directly determine whether an agent selects your service over a competitor's. Clear verbs, well-structured parameters, and accurate descriptions will matter as much as keyword optimization does today. If commerce starts flowing through agents, and all signs suggest it will, the websites with well-structured, reliable WebMCP tools will capture that traffic. The ones without them will not exist in the agent's decision space. That is a familiar kind of existential pressure. It is exactly what built the SEO industry. The Bigger Picture: One Protocol, Multiple Surfaces WebMCP does not exist in isolation. It is part of a rapidly assembling stack of protocols that collectively define how the agentic web will work. MCP (Model Context Protocol), created by Anthropic, handles backend tool integration. It lets AI agents call server-side functions through a standardized interface. A2A (Agent-to-Agent protocol) handles communication between AI agents themselves. NLWeb, led by Microsoft, provides a framework for making website content queryable through natural language. And MCP Apps, announced on January 26, 2026 as the first official MCP extension, enables tools to return interactive UI components that render directly in conversations inside clients like Claude, ChatGPT, and VS Code. WebMCP adds the final piece: a client-side standard for exposing web application functionality directly to agents operating in the browser. MCP for backends. WebMCP for frontends. MCP Apps for interactive experiences within AI clients. One protocol family. Multiple surfaces. Open source. The infrastructure of the agentic web is taking shape faster than most people realize. If you squint, you can see the outlines of a world where websites serve two parallel audiences from a single codebase, where "browsing" means something entirely different when your agent does it for you, and where the quality of your machine-readable interface matters as much as the quality of your visual design. Human in the Loop: The Security Question No serious discussion of WebMCP can avoid the security implications. The specification's authors acknowledge this directly, and the list of open concerns is substantial. The MCP ecosystem has already demonstrated real-world vulnerabilities. Prompt injection attacks against GitHub's MCP server allowed AI coding assistants to exfiltrate private repository contents. Malicious MCP server packages have been caught injecting BCC copies of all email communications to attacker-controlled servers. The WhatsApp MCP server was compromised through a "tool poisoning" attack that silently exfiltrated users' entire message histories. These are not theoretical risks. They have already happened. WebMCP inherits all of these concerns and adds browser-specific ones. Because tools execute in the page's JavaScript context with the user's session, a compromised tool has access to everything the user has access to. The specification identifies two critical trust boundaries: when a website registers tools (exposing information about its capabilities), and when an agent calls a tool (executing code with the user's privileges). The proposed mitigations are sensible but incomplete. The browser enforces origin-based permissions, so tools only work on the domains that registered them. A requestUserInteraction() mechanism pauses agent execution to ask for explicit user confirmation before sensitive actions. The declarative API's default behavior is to populate form fields visually and wait for the user to click submit, keeping the human in the loop. But the harder problems remain open. How do you prevent prompt injection through tool descriptions? What happens when two agents operate on the same page and stomp each other's actions? How do you handle the "rug pull" scenario where a tool changes its behavior after gaining trust? What about data exfiltration through tool chaining, where a series of individually innocent tool calls combine to extract sensitive information? The specification acknowledges these questions without resolving them. That is appropriate for an early draft, but it means that anyone building with WebMCP today needs to approach it with eyes wide open. This is experimental technology. The security model is incomplete. The API surface will almost certainly change before it stabilizes. Practical Implications for Developers If you are a developer evaluating WebMCP, here is what matters right now. First, the barrier to entry is genuinely low. If your application already has well-structured HTML forms, the declarative API requires literally two HTML attributes to make them agent-readable. You do not need to rewrite your frontend, deploy new infrastructure, or learn a new framework. This is the kind of progressive enhancement that the web was designed for. Second, applications with clean separation between UI and business logic will have an easier time. If your app's logic is tangled up in React component state or a Redux store, you will need to expose that data through a shared service layer before WebMCP tools can do anything useful. Tightly coupled SPAs will require refactoring. This is yet another argument for clean architecture, the kind of architectural thinking that pays dividends across every technology transition. Third, start with high-value, low-complexity tools. Product search, store locators, contact forms, and simple CRUD operations offer immediate wins with minimal implementation effort. Deploy declarative tools first. Use the Model Context Tool Inspector extension (already available on the Chrome Web Store) to test and debug. Only move to imperative tools when you need multi-step workflows or dynamic state management. Fourth, treat tool descriptions like API documentation. Clear, specific descriptions help agents understand when and how to use your tools. Vague descriptions lead to misuse. The WebMCP best practices section reads like conversion copywriting guidance: use clear verbs, explain the reasoning behind options, prefer positive descriptions. The quality of your tool metadata directly determines whether agents will prefer your service over a competitor's. Fifth, monitor and measure. Track which agents call your tools, success rates, common error patterns, and execution times. This data will become as important as web analytics. "Agentic CRO" (conversion rate optimization for AI agents) is not a buzzword. It is a real discipline that is about to emerge. What WebMCP Does Not Change It is worth being explicit about the limitations. WebMCP does not eliminate traditional UIs. Human users still need visual interfaces, accessibility features, and exploratory browsing capabilities. Tools augment experiences rather than replacing them. Your website still needs to work beautifully for people who visit it directly. WebMCP does not solve the discovery problem yet. Tools only exist when a page is open in a tab. An agent cannot know what tools Gmail offers without navigating there first. The manifest-based discovery mechanism is proposed but not specified. Until that piece ships, WebMCP is most useful for agents that are already on your site. WebMCP is not a Chrome-exclusive feature. It is being developed as an open web standard through the W3C Web Machine Learning Community Group. Firefox, Safari, and Edge are participating in the working group but have not shipped implementations yet. The cross-vendor authorship (Microsoft and Google) is a strong signal that broader support is coming, but there are no timelines from other browsers. And perhaps most importantly, WebMCP is a DevTrial. The API surface will change. Method names, parameter shapes, the entire navigator.modelContext interface could shift between Chrome versions. Experiment with it. Build prototypes. Do not ship it to production. The Question That Matters Every major web standard has followed a pattern. A draft specification appears. Early adopters experiment. The tooling matures. Adoption reaches a tipping point. And then the laggards scramble to catch up, paying a premium in both effort and lost opportunity. WebMCP is at the very beginning of that curve. Chrome 146 stable is expected around March 2026. The specification is a Draft Community Group Report, not a standard. The security model is incomplete. The discovery mechanism does not exist. Most developers have never heard of it. And yet the trajectory is unmistakable. MCP for backends. MCP Apps for interactive content. WebMCP for frontends. A2A for agent communication. NLWeb for content queries. The agentic web is not a future vision. It is being assembled right now, in public, by the same companies that control the browsers. The question is not whether this will matter. The question is whether you will be ready when it does. The developers who understand navigator.modelContext today will be the ones agents prefer tomorrow. The businesses that think about their tool surface, not just their visual surface, will capture the next generation of web traffic. We have been here before. We know how this story goes. The web built for machines does not replace the web built for humans. It runs alongside it, amplifying reach for those who invest in it and quietly erasing those who do not. WebMCP is the signal. The rest is up to us. Final Thoughts If this kind of analysis resonates with you, consider following me for more long-form perspectives on how AI is reshaping software engineering, architecture, and the web. I write from over two decades of experience building real systems in fintech, blockchain, and high-traffic environments, not from hype cycles. You can find me at ivanturkovic.com, on LinkedIn, and on Threads. If you want to discuss WebMCP, its implications for your product, or anything else covered here, I would love to hear from you. Reach out through any of those channels. And I am genuinely curious: what do you think? Is WebMCP the beginning of a fundamental shift in how the web works, or is it another standard that will languish in committee? Are you already experimenting with it? What tools would you expose first? Drop your thoughts in the comments or send me a message. The best conversations happen when smart people disagree. --- # No, Average People Will Not Build Their Own Software With AI URL: https://www.ivanturkovic.com/2026/02/14/average-people-will-not-build-software-with-ai/ Published: 2026-02-14 Topics: AI, Software Engineering There is a narrative gaining traction in tech circles, on social media, and in breathless conference keynotes that goes something like this: AI will soon let anyone build their own software. Need a budgeting app? Just describe it to an AI and it will create one for you. Want a custom CRM for your small business? A few prompts and you are up and running. Personal software, built on demand, by anyone, for anything. It sounds revolutionary. It also fundamentally misunderstands what software actually is. I have spent more than twenty years building, deploying, and maintaining production software across fintech, blockchain, and high-traffic platforms. What I have learned in that time is that writing code has never been the hardest part of software development. It is everything else. And that everything else is precisely what this "everyone can build software" narrative conveniently ignores. The Seductive Simplicity of the Demo If you have watched any AI coding demonstration in the last two years, you have seen something genuinely impressive. Someone types a natural language prompt, and within seconds, working code appears. A to-do app. A landing page. A simple game. The audience gasps. Twitter threads go viral. The conclusion seems obvious: if AI can build a working app in sixty seconds, why would anyone pay a developer? But this is the demo trap. Demos are designed to show the best possible outcome in the most controlled possible environment. They work because the problem is well-defined, the scope is tiny, the requirements are clear, and nobody has to use the result in production for more than five minutes. It is the software equivalent of assembling IKEA furniture in a showroom and concluding that anyone can build a house. The gap between a working demo and a working product is not a small one. It is an ocean. And most of the challenges that make that ocean so vast have nothing to do with writing lines of code. Software Is Not Code This is the most important distinction that the "anyone can build software" narrative misses entirely. Code is a component of software, but software is vastly more than code. Software is a living system that includes product design, architecture decisions, data modeling, security considerations, error handling, performance optimization, deployment infrastructure, monitoring, user feedback loops, and ongoing maintenance. Code is the bricks. Software is the building, the plumbing, the electrical, the foundation, the zoning permits, and the decades of upkeep. When someone asks an AI to "build me a budgeting app," what they are really asking for involves questions they have not thought to ask yet. Where does the data live? How do you handle authentication? What happens when the app crashes at 2 AM and all their financial data is in limbo? How do you back up the database? How do you handle a user who enters invalid data? What about privacy regulations? What happens when the AI model that generated the code gets updated and the code no longer works the same way? These are not edge cases. These are the actual substance of software development. And they require precisely the kind of thinking that average users are not equipped, trained, or interested in doing. Not because they are incapable, but because they have their own expertise, their own careers, their own priorities. They should not have to become software engineers just to use a custom tool. Product Thinking Is the First Barrier Before a single line of code gets written, someone has to decide what the software actually needs to do. Not in vague, aspirational terms, but in specific, concrete, sometimes painfully detailed terms. This is product thinking, and it is a skill that takes years to develop. What features are essential versus nice to have? What is the user flow for the most common task? What happens when a user tries to do something unexpected? How should the system behave when it has no data versus when it has ten years of data? What are the performance requirements? Who are the actual users, and what do they actually need versus what they say they need? Most people, when they imagine a software tool they want, are picturing the happy path. They see the ideal scenario where everything works perfectly, the data is clean, the user does exactly the right thing, and the output is exactly what they expected. Professional software engineers spend roughly eighty percent of their time on everything that happens off the happy path. Error states. Edge cases. Concurrent access. Data corruption. User confusion. Graceful degradation. AI cannot do this product thinking for you, because product thinking is about understanding a problem deeply enough to anticipate what you have not yet imagined. It requires context about the business, the users, the constraints, and the tradeoffs. It requires judgment. It requires saying "no" to features that seem good in isolation but create complexity that will cripple the system later. This is not a prompting problem. It is a thinking problem. And it is one of the reasons professional software development exists as a discipline. Debugging Is Where Reality Sets In Every experienced developer has a version of this story: you ask AI to generate some code, it looks right, it runs, and then three days later something breaks in a way that makes no sense until you spend four hours tracing through logic you did not write and do not fully understand. Debugging AI-generated code is often harder than debugging code you wrote yourself, precisely because you lack the mental model of how it was constructed. For a professional developer, this is manageable. You understand the language, the frameworks, the common failure patterns. You know what stack traces mean. You know how to isolate a problem, form a hypothesis, and test it. You know that the bug is probably not where you think it is, and you have the experience to navigate that uncertainty. For an average person who just wanted a budgeting app? A cryptic error message is a dead end. A silent data corruption bug is invisible until the damage is done. A race condition that only manifests under certain timing is effectively unsolvable without deep technical knowledge. The AI might help you generate the initial code, but when that code breaks, and it will break, you are standing in unfamiliar territory with no map. This is not a temporary limitation that will be solved with better AI. Debugging is fundamentally about understanding systems at a level of depth that requires real knowledge. Even when AI gets better at debugging, someone still needs to understand the output well enough to trust it, verify it, and know whether the fix introduced three new problems. That someone, by definition, is a developer. Architecture Decisions Compound Over Time Architecture is the set of decisions you make about how parts of a system fit together. It includes how data flows through the application, how different components communicate, how you structure your database, how you handle state, how you manage dependencies, and dozens of other structural choices that shape everything the software can and cannot do in the future. These decisions are invisible at the beginning. When your app has ten users and one feature, architecture barely matters. Any structure will work. But architecture decisions compound. A bad data model at the start becomes a massive migration problem two years later. A monolithic architecture that was fine for a prototype becomes a deployment bottleneck when you need to scale. A tight coupling between two components that seemed harmless means you cannot change one without breaking the other. AI, as it stands today, does not do architecture. It generates code in response to prompts, but it does not reason about how today's decision will affect next year's requirements. It does not know that the quick solution it suggested will create technical debt that takes months to unwind. It does not understand your business context well enough to know that the database schema it generated will not handle the regulatory requirements you will face in six months. Average users have no reason to think about architecture because they have no reason to know it exists. They see the surface of software. Professionals see the structure underneath, and they know that the structure is what determines whether the software survives its first encounter with reality. Deployment Is Its Own Discipline You have code. It runs on your laptop. Congratulations. Now what? Where does this software actually live? How do users access it? How do you handle DNS, SSL certificates, server configuration, container orchestration, environment variables, CI/CD pipelines, database hosting, backup strategies, logging, monitoring, and alerting? Deployment is where a shocking number of AI-built projects go to die. It is one thing to generate code in a sandbox. It is another to get that code running reliably on infrastructure that real people depend on. And "reliably" is the key word. Anyone can get something running once. Getting it to stay running, under load, through updates, across failures, is what operations engineering is all about. I have spent significant portions of my career working with Docker, Kamal, firewall configurations, and deployment automation. These are complex, interconnected systems where a single misconfiguration can take down a production environment. The idea that someone with no technical background is going to navigate this landscape because AI wrote them some Python code is, with respect, not grounded in reality. Yes, there are platforms that abstract some of this away. Vercel, Heroku, Railway, and similar services handle deployment for certain types of applications. But abstraction is not elimination. The complexity still exists. It is just hidden until something goes wrong, at which point you need someone who understands what is underneath the abstraction to fix it. Iteration Never Stops Software is never done. This is one of the hardest things for non-technical people to understand, and it is one of the biggest reasons the "build your own software" dream falls apart. You do not build software once and walk away. You build it, release it, discover it does not quite work the way you expected, gather feedback, fix bugs, add features, adjust to changing requirements, update dependencies, patch security vulnerabilities, and repeat this cycle indefinitely. Professional software teams spend the vast majority of their time not building new things but maintaining and improving existing things. Studies consistently show that maintenance accounts for sixty to eighty percent of the total cost of software over its lifetime. This is not a failure of process. It is the inherent nature of software. Requirements change. Users behave unexpectedly. External APIs change their interfaces. Operating systems release updates. Security threats evolve. The world changes, and software must change with it. When proponents of AI-built personal software describe their vision, they almost always describe the creation moment. The prompt, the generation, the first working version. They almost never describe what happens on day thirty, or day three hundred. Who updates the dependencies when a critical security vulnerability is discovered? Who migrates the database when the schema needs to change? Who fixes the integration when the third-party API you depend on releases a breaking change? The answer, overwhelmingly, is: nobody. Because the average person who generated that code has neither the skills nor the desire to maintain it. And unmaintained software is not just useless. It can be actively dangerous, especially when it handles personal data, financial information, or anything that matters. Maintenance Is the Silent Killer Let me put this plainly: most people do not want to maintain software. They want finished tools that work. This is not a character flaw. It is a perfectly rational preference. When you buy a microwave, you do not expect to rebuild the magnetron every six months. When you use Google Sheets, you do not worry about the database engine underneath. You use the tool and get on with your life. The entire history of consumer technology is a story of taking complex, high-maintenance systems and turning them into reliable, low-maintenance products that normal people can use without thinking about the internals. This is what companies do. They absorb the complexity of maintenance so that users do not have to. The "build your own software" narrative essentially proposes reversing this entire trajectory. Instead of professional teams maintaining robust products, individuals would maintain their own custom software. Instead of spreading the cost of maintenance across millions of users, each person would bear the full cost of their own system. This is not progress. It is regression. It is asking people to become their own IT departments, their own QA teams, their own DevOps engineers, their own product managers. There is a reason companies employ entire teams to keep software running. It is not because they enjoy spending money. It is because software maintenance is genuinely hard, ongoing, and essential. The idea that AI eliminates this requirement is wishful thinking of the highest order. The Spreadsheet Analogy Is Revealing Advocates of the "everyone builds software" future often point to spreadsheets as precedent. Excel, they argue, turned millions of non-programmers into builders. Regular people create complex spreadsheets all the time. Is AI-assisted coding not just the next step? The analogy is more revealing than they intend. Because the world is absolutely full of broken, unmaintainable, business-critical spreadsheets. There is an entire genre of corporate horror stories about multi-million dollar decisions based on spreadsheets with hidden errors, circular references, and formulas that nobody understands anymore because the person who built it left the company three years ago. JP Morgan lost $6 billion in the "London Whale" incident, and a contributing factor was a flawed Value at Risk model built in Excel with copy-paste errors. The European Spreadsheet Risks Interest Group maintains a catalog of spreadsheet horror stories that would make any CFO lose sleep. This is what happens when non-specialists build critical tools without engineering discipline. Now imagine that same dynamic, but instead of spreadsheets, it is custom software handling authentication, personal data, financial transactions, and API integrations. The failure modes are not miscalculated totals. They are data breaches, corrupted databases, and security vulnerabilities. The spreadsheet analogy does not support the "everyone builds software" argument. It undermines it. Security Is Not Optional Of all the things that the "average person builds software" narrative glosses over, security might be the most dangerous omission. Writing secure software is hard. It is hard for experienced professionals who think about it constantly. For someone who just wanted a budgeting app and asked AI to generate it, security is an afterthought at best and invisible at worst. SQL injection. Cross-site scripting. Insecure authentication. Improper data storage. Missing encryption. Overly permissive API endpoints. Exposed environment variables. Every one of these is a common vulnerability that AI-generated code can and does produce, and every one of them can lead to real harm when the software handles real data. The average person building software with AI has no way to evaluate whether the generated code is secure. They do not know what to look for. They do not know what questions to ask. They cannot perform a code review or a penetration test. They cannot assess whether the authentication implementation is sound or whether the data storage approach complies with relevant regulations. And AI, while improving, regularly produces code with security flaws that would make any experienced developer wince. This is not a problem that gets solved by better AI. Security is adversarial. Attackers actively probe for weaknesses, and the weakest targets will always be unmaintained, unreviewered software built by people who do not understand the threat model. A world where millions of individuals build and deploy their own software is a world where the attack surface expands exponentially. What AI Actually Does Well None of this means that AI is useless for software development. It means the value is different from what the hype suggests. AI is an extraordinary tool for people who already understand software. It accelerates professionals. It does not replace them. AI excels at generating boilerplate code that experienced developers would otherwise write by hand. It is excellent at translating well-specified requirements into initial implementations. It is useful for exploring unfamiliar APIs, generating test cases, and scaffolding projects. It is a powerful assistant for code review, documentation, and refactoring. In the hands of a skilled developer who can evaluate, modify, and maintain the output, AI is a genuine productivity multiplier. The key phrase in that paragraph is "in the hands of a skilled developer." AI amplifies capability. It does not create it from nothing. A senior developer with AI tools can do the work that previously required a small team. A junior developer with AI tools can accelerate their learning curve and handle tasks above their experience level with appropriate supervision. But a non-developer with AI tools can create something that looks like software in the same way that a non-pilot with autopilot can keep a plane level in clear skies. The moment turbulence hits, the illusion of competence disappears. What People Actually Want When non-technical people say they want to "build their own software," what they usually mean is one of three things. First, they want an existing tool to be customizable enough to fit their specific workflow. Second, they want someone to build something custom for them without the traditional cost and timeline of software development. Third, they want to automate a specific task that currently requires manual effort. All three of these are real needs, and AI is genuinely helpful for all three, but not in the way the "build your own software" narrative suggests. Better customization means better products, not personal software. Cheaper custom development means AI-augmented developers building for clients more efficiently, not clients building for themselves. Better automation means smarter integrations and workflows, not custom applications. The real impact of AI on software for non-technical users is not that they will build their own tools. It is that the tools built for them will be better, more personalized, and more rapidly developed. The value chain does not disappear. It compresses. And the people who benefit most are still the ones who understand the full stack of what software actually requires. The Historical Pattern Is Clear We have been here before. Every generation of technology produces a wave of predictions that programming is about to become accessible to everyone and professional developers will become obsolete. COBOL was going to let business managers write their own software. Fourth-generation languages were going to eliminate the need for programmers. Visual Basic was going to democratize application development. No-code and low-code platforms were going to make developers redundant. Every single time, the prediction followed the same arc: initial excitement, genuine productivity improvements for specific use cases, and then the quiet realization that the hard parts of software development have nothing to do with syntax. Each of these technologies delivered real value. COBOL made business applications more accessible. Visual Basic genuinely expanded who could create Windows applications. No-code platforms are legitimately useful for specific, well-scoped problems. But none of them eliminated the need for professional software developers. If anything, each wave increased demand for developers by making software more prevalent, more ambitious, and more critical to business operations. AI is the latest iteration of this pattern, and it is a more powerful one. The productivity gains are real and significant. But the fundamental dynamics have not changed. The hard parts are still hard. The boring parts are still essential. And the gap between a demo and a production system is still measured in months of skilled effort, not minutes of prompting. The Real Question Nobody Is Asking Instead of asking "will AI let everyone build software?" we should be asking "should everyone build software?" The answer, I believe, is clearly no. Not because people are incapable, but because it is a terrible allocation of human attention. A doctor's time is better spent treating patients than debugging a custom appointment scheduling app. A teacher's time is better spent educating students than maintaining a custom grade tracking system. A small business owner's time is better spent running their business than troubleshooting deployment failures. Specialization exists because it works. We do not ask doctors to design their own medical equipment or teachers to manufacture their own textbooks. We have professionals who do these things well, at scale, so that others can focus on what they do best. Software is no different. The dream of universal software creation sounds empowering until you realize it is actually asking everyone to take on a second job they did not want and are not trained for. The more productive vision, and the one I believe will actually materialize, is that AI makes professional software development faster, cheaper, and more responsive. That the cycle time between "I need a tool" and "here is your tool" shrinks dramatically. That bespoke software becomes economically viable for smaller organizations. That customization becomes deeper and more accessible within professionally built products. This is a vision where AI amplifies the value of skilled developers rather than rendering them unnecessary. Where This Narrative Comes From It is worth considering why the "everyone builds software" narrative is so popular despite its obvious limitations. Part of it is genuine excitement about a powerful technology. AI coding tools are impressive, and the instinct to extrapolate from impressive demos is natural. But part of it is also driven by commercial interests. Companies selling AI tools benefit from the narrative that their product eliminates the need for expensive developers. Venture capitalists funding AI startups benefit from the story that the total addressable market includes every human being, not just professional developers. Conference organizers benefit from provocative claims that fill seats. The "everyone can code" narrative is good for business, even if it is not good analysis. There is also a cultural dimension. In tech culture, there is a persistent belief that any sufficiently complex problem can be abstracted away with the right tool. This belief has produced genuine breakthroughs, but it has also produced a blindspot: the assumption that because something is technically possible, it is practically likely. Yes, it is technically possible for a non-developer to use AI to create a working application. It is also technically possible for me to perform my own dental work. The question is not possibility but probability, practicality, and wisdom. The Bottom Line AI is a transformative tool for software development. It is not a replacement for software developers. The skills that make software work in the real world, product thinking, architectural judgment, debugging expertise, deployment knowledge, security awareness, and the patience for ongoing maintenance, are not eliminated by AI. They are made more valuable. The average person will not build and maintain their own software for the same reason the average person does not build and maintain their own car, plumbing, or electrical system. Not because they are stupid, but because it is a specialized discipline that requires deep knowledge, ongoing attention, and years of experience to do well. AI lowers the barrier to entry for writing code. It does not lower the barrier to entry for building software. And until we stop conflating the two, we will keep having this same conversation every time a new technology makes coding look easy. The future is not everyone building software. The future is better software, built faster, by people who understand what it takes to make it work. That is the real opportunity, and it is more than exciting enough without the fantasy. Final Thoughts If this post resonated with you, I would love to hear your perspective. Are you a developer navigating the AI wave? A non-technical leader trying to separate hype from reality? Either way, the conversation matters. You can follow me on LinkedIn and Threads where I share regular thoughts on AI, software engineering, and building technology that lasts. If you have questions or want to discuss any of these ideas further, feel free to reach out through my blog's contact page. I would genuinely like to know what you think. Drop a comment below or send me a message. Whether you agree, disagree, or see it differently from your own experience, the best insights come from real conversations with real builders. --- # Full-Time CTO vs. Fractional: The Real Math Nobody Shows You URL: https://www.ivanturkovic.com/2026/02/13/full-time-cto-vs-fractional-the-real-math-nobody-shows-youthe-math-on-hiring-a-full-time-cto-and-why-it-rarely-adds-up/ Published: 2026-02-13 Topics: Startups & Business Every startup founder eventually faces the same inflection point. The product is gaining traction. Technical decisions are getting more complex. Investors are asking who's driving the technology strategy. The board wants a name next to the CTO title. And so the search begins for the person who will own the technical vision, build the engineering team, and make the architectural decisions that will define the company's trajectory for years to come. It sounds like a straightforward hire. Find someone brilliant, give them the title, and let them build. But the reality of hiring a full-time Chief Technology Officer in the US market is a story written in hidden costs, extended timelines, misaligned incentives, and opportunity losses that most founders don't fully appreciate until they're deep into the process. Let's do what surprisingly few founders do before launching this search. Let's actually run the numbers. The Sticker Price That Isn't the Real Price When founders think about hiring a CTO, they usually start with the salary. In the US market, particularly in major tech hubs like San Francisco, New York, Austin, or Seattle, a qualified CTO with the experience to lead a growing startup commands a base salary in the range of $300,000 to $400,000. Let's use $325,000 as a reasonable midpoint for a Series A or Series B company that needs real technical leadership but isn't yet at the scale of a public company. That number alone should give any founder pause. But it's just the beginning. Employer payroll taxes in the US add roughly 7.65% for FICA (Social Security and Medicare), plus state unemployment insurance that varies by location. For a $325,000 salary, you're looking at approximately $25,000 to $30,000 in employer-side tax obligations, depending on your state. Then there's the benefits package. A competitive executive benefits package in the US typically includes health insurance (employer contribution for a family plan runs $20,000 to $28,000 annually), dental and vision coverage, a 401(k) match (commonly 4% to 6% of salary, so $13,000 to $19,500), life insurance, disability coverage, and increasingly, wellness stipends and executive perks. Conservatively, you're adding $40,000 to $55,000 in total benefits cost. Now add the recruitment cost. Executive search firms in the US typically charge 25% to 33% of first-year total compensation for C-suite placements. At 25% of a $325,000 base, that's $81,250. Some firms charge against total compensation including expected bonus, which pushes it higher. And that fee is due whether the hire works out or not. Let's put this together. Cost ComponentAnnual AmountBase Salary$325,000Employer Payroll Taxes (FICA + State)$28,000Benefits Package (Health, 401k, Insurance)$48,000Recruitment Fee (25% of base)$81,250Equity (1-2% of company)Literally pricelessYear One Total (before a single decision)$482,250+ Nearly half a million dollars. And we haven't even discussed the equity yet. The Equity Question Nobody Wants to Quantify In the US startup ecosystem, a CTO hire at the Series A or B stage typically receives between 1% and 3% equity, depending on the company's stage, existing cap table, and the candidate's leverage. At early stages, this can be even higher. Let's say your company is valued at $20 million at the time of the CTO hire. A 1.5% equity grant represents $300,000 in current paper value. But equity in a startup isn't static. If the company grows to a $200 million valuation (the kind of trajectory that justifies a full-time CTO in the first place), that same 1.5% is now worth $3 million. This is the number that founders rarely confront honestly. You're not just paying someone half a million dollars a year. You're giving away a piece of the upside that you, your co-founders, your early employees, and your investors have been building toward. And unlike salary, equity dilution compounds. Every percentage point you grant to a CTO who stays 26 months is a percentage point that doesn't go to the next CTO, to key engineers, or back to the founding team. The standard four-year vesting schedule with a one-year cliff provides some protection, but even a CTO who leaves after two years walks away with half their equity grant. For a hire that cost nearly $500,000 in cash during their first year alone, you've now also permanently diluted your cap table. The Timeline Problem: 14 Months of Searching The financial costs are significant, but they're at least predictable. What catches most founders off guard is the time cost. According to data from executive search firms and platforms like Hunt Club, Riviera Partners, and various industry surveys, the average time to fill a CTO position in the US is between 12 and 16 months. Let's call it 14 months as a reasonable benchmark. Fourteen months is not a neutral period. It's fourteen months during which your technical strategy is either stalled, being driven by someone who doesn't have the authority or context to make the right calls, or being managed by a founder who is simultaneously trying to raise capital, close customers, and build a team. Every month of that search is a month where architectural decisions are deferred, technical debt accumulates unchecked, engineering hires are made without a coherent leadership vision, and competitors with established technical leadership are executing faster. There's also a compounding effect that's easy to miss. The longer the search takes, the more desperate the hire becomes. Founders who've been searching for a year start making compromises they wouldn't have made at month three. They settle for someone who's 70% right instead of holding out for the person who truly fits. And a compromised CTO hire is often worse than no CTO at all, because they'll make decisions that are expensive to reverse and create organizational dynamics that are painful to unwind. During this search period, your company is also burning cash on the search process itself. Executive search retainers typically start at $30,000 to $50,000 upfront, with the remainder due on placement. If your first search fails (and roughly 40% of executive searches are either abandoned or restart with a different firm), you're looking at additional costs with no placement to show for it. The Ramp Time Reality: Six Months Before Real Output Let's say the search succeeds. You've found your CTO. They've accepted the offer, negotiated their equity, and they start on Monday. Now what? They don't know your codebase. They don't know your infrastructure. They don't know your team's dynamics, your technical debt landscape, your deployment pipeline, your vendor relationships, your compliance requirements, or the seventeen things your previous technical lead tried that didn't work. They don't know why that one microservice is still on an old version of Node, or why the database schema looks the way it does, or which engineer is the real load-bearing wall of the organization. A new CTO at a growing company typically needs four to six months before they can make confident, informed decisions at the strategic level. During the first two months, they're learning the stack, meeting the team, and understanding the product. Months three and four involve identifying what needs to change and building the political capital to make those changes. By months five and six, they're beginning to implement their vision. That's six months of salary, benefits, and equity vesting for someone who is, by necessity, operating at reduced capacity. Not because they're incompetent, but because context takes time. Nobody walks into a complex technical organization and starts making great decisions on day one. The ones who try usually cause more damage than the ones who take time to learn. So let's update our timeline. Fourteen months searching. Six months ramping. That's twenty months from the moment you decide you need a CTO to the moment your CTO is actually operating at full capacity. The Tenure Trap: 26 Months and Counting Down Here's the number that should reshape how every founder thinks about this decision. The average tenure of a CTO at a venture-backed US startup is approximately 26 months. Some sources put it slightly higher, around 30 months, while others (particularly at earlier-stage companies) report figures closer to 24 months. Think about what that means in the context of our timeline. If you spent 14 months finding them and 6 months onboarding them, and their total tenure is 26 months, you're getting roughly 6 to 12 months of peak performance from someone who cost nearly $500,000 in year one alone and is vesting equity every month. Why do CTOs leave so quickly? The reasons are well documented. The role changes faster than almost any other C-suite position. A CTO who was perfect for building the initial product may not be the right person to scale the platform. A CTO who excels at scaling infrastructure may struggle with the organizational and political demands that come at the Series C stage and beyond. Misalignment with the CEO on technical direction is common. Burnout is endemic. And the US market is relentlessly competitive for senior technical talent, which means your CTO is fielding recruiter calls from month 18 onward. There's also a psychological dimension that's rarely discussed. CTOs who join with significant equity have a natural incentive to stay through their cliff and then begin evaluating their options. The one-year cliff creates an artificial loyalty period, and the subsequent monthly vesting creates a gradually weakening retention mechanism. By the time they've vested half their grant at the 24-month mark, the financial incentive to stay has diminished significantly relative to the opportunity cost of staying in a role that may no longer fit. The Complete Cost Picture Let's bring all of these elements together into a single, honest picture of what a full-time CTO hire actually costs over a realistic timeline. PhaseDurationDirect CostOpportunity CostExecutive Search14 months$81,250 (placement fee) + $30,000 (retainer)Technical strategy gap, deferred decisions, accumulating debtYear 1 Compensation12 months$401,000 (salary + taxes + benefits)First 6 months at reduced strategic outputYear 2 Compensation12 months$413,000 (with typical 3% raise)Peak performance, but likely exploring options by month 18Equity (1.5% over 26 months)26 months~50% vested at departurePermanent cap table dilutionSeverance (if applicable)N/A$80,000-$160,000 (3-6 months typical)Leadership vacuum, team uncertaintyTotal Cash Outlay40 months end-to-end$1,005,250 - $1,085,250Plus equity dilution Over a million dollars and 40 months of elapsed time for roughly 12 months of peak CTO performance. And then you start the entire cycle again. The Hidden Costs Nobody Puts in a Spreadsheet The direct financial costs are staggering enough, but there are several categories of hidden cost that compound the problem. Team instability. Engineering teams are remarkably sensitive to leadership changes. When a CTO departs, you don't just lose the CTO. You risk losing the engineers they hired, the ones who joined because of a vision the departing CTO articulated. Studies from organizations like the Society for Human Resource Management suggest that executive departures trigger 15% to 25% additional attrition in their reporting chain within six months. For a 20-person engineering team, that could mean losing three to five engineers, each of whom costs $50,000 to $100,000 to replace. Strategic whiplash. Every CTO brings their own architectural philosophy, technology preferences, and organizational model. CTO number one builds a monolith. CTO number two wants microservices. CTO number three thinks you should have stayed with the monolith but with better boundaries. Each transition doesn't just change direction; it creates a period of strategic paralysis where the team doesn't know which decisions will survive the next leadership change. This uncertainty slows execution far more than most founders realize. Institutional knowledge loss. A CTO who leaves after 26 months takes with them an enormous amount of context that exists nowhere in documentation. Why certain architectural decisions were made. Which vendor relationships are fragile. Where the real security risks live. What the board was told versus what's actually true about the technical roadmap. This knowledge loss is invisible on a balance sheet but devastating in practice. Board and investor confidence. In the US venture ecosystem, CTO turnover is a yellow flag for investors. One CTO departure is expected. Two departures within three to four years raise serious questions about the founder's ability to attract and retain technical leadership, or worse, about the underlying health of the technical platform. This perception can affect your ability to raise future rounds on favorable terms. Enter the Fractional CTO A fractional CTO is an experienced technical leader who works with your company on a part-time or contracted basis, typically two to four days per week, providing the same strategic, architectural, and leadership functions as a full-time CTO at a fraction of the cost and commitment. In the US market, a qualified fractional CTO typically costs between $15,000 and $25,000 per month, which translates to $180,000 to $300,000 annually depending on engagement scope, hours, and the fractional's experience level. Let's use $220,000 as a reasonable midpoint for a seasoned fractional CTO engaged three days per week. Here's what that number includes that the full-time hire doesn't. No recruitment fee. You're not paying a search firm $80,000+ to find a fractional CTO. The fractional CTO market operates through direct networks, specialized platforms, and referrals. Most engagements begin within two to four weeks of initial conversation. No equity. Fractional CTOs typically work on a cash-only basis. Your cap table stays clean. Your existing shareholders don't get diluted. And there's no complex equity negotiation adding weeks to the hiring process. No benefits overhead. As a contracted engagement, you're not providing health insurance, 401(k) matching, disability coverage, or any of the other benefits that add $48,000+ to a full-time hire's cost. No severance risk. If the engagement isn't working, you can adjust scope or end the relationship with standard notice periods (typically 30 to 60 days), not months of severance negotiation and legal review. No ramp time. This is perhaps the most undervalued advantage. A good fractional CTO has done exactly this before, often for five, ten, or twenty companies. They've seen your codebase's problems in other codebases. They've navigated your scaling challenges at other startups. They've built the team structures you need at companies just like yours. Where a full-time hire needs six months to build context, a fractional CTO is typically making informed strategic contributions within the first two weeks. The Side-by-Side Comparison FactorFull-Time CTOFractional CTOAnnual Cash Cost$482,250+ (Year 1)$180,000 - $300,000Recruitment Cost$81,250+$0Equity Dilution1-3%NoneTime to Start14 months average2-4 weeksTime to Full Productivity6 months after start2-4 weeksSeverance Risk3-6 months salary30-60 day noticeAverage Tenure26 monthsFlexible (ongoing or project-based)Pattern RecognitionOne company's contextCross-company experienceScalabilityFixed (one person, full-time)Adjustable (scale up or down with needs) When Full-Time Actually Makes Sense I want to be honest about this comparison because it isn't always one-sided. There are specific situations where a full-time CTO is the right choice. If your company has reached a scale where technology is the product (not just the means of delivery), where you have 50+ engineers who need a full-time leader present in every standup, every architecture review, and every crisis, a fractional model may not provide enough hours in the week. At Series C and beyond, when the organizational complexity demands a full-time executive presence on the leadership team, the math shifts. If you're building deep technology (AI research, novel hardware integration, breakthrough algorithmic work) where the CTO needs to be hands-on in the code and the research daily, fractional engagement may not provide the depth of immersion required. And if you've found someone who is genuinely a co-founder in spirit, someone whose technical vision is so aligned with the company's mission that they're willing to bet their career on it, the full-time commitment (with the equity that represents that bet) can create alignment that a fractional relationship simply cannot replicate. But here's the thing. Most companies searching for a CTO are not in any of these situations. They're seed-stage or Series A companies that need architectural guidance, technical strategy, engineering hiring support, and someone credible to represent the technology to investors and partners. They don't need someone sitting in the office five days a week. They need someone who knows what they're doing, available when it matters, and not burning through half a million dollars a year while they figure out the company's codebase. The Pattern Recognition Advantage There's one advantage of fractional CTOs that doesn't show up in any cost comparison but might be the most valuable of all: pattern recognition across companies. A full-time CTO, no matter how talented, has one data point: your company. They're solving your problems with insights from their previous roles, which may or may not be relevant to your current context. They're learning in real time, which means they're making some mistakes that were avoidable. A fractional CTO who has worked with ten, fifteen, or twenty companies has seen your exact problem before. They've watched three other startups try to migrate from a monolith to microservices too early and can tell you exactly why it failed and what to do instead. They've seen the hiring mistake you're about to make because they saw the same resume red flags at another company last quarter. They've navigated the AWS cost spiral, the database scaling cliff, the "we need to rewrite everything" panic, and the "our lead engineer just quit" crisis multiple times. This cross-pollination of experience is something that no full-time hire can replicate, regardless of how brilliant they are. It's the difference between a doctor who has treated one patient extensively and a doctor who has treated hundreds of patients with similar symptoms. Both are valuable. But when you're trying to diagnose a problem quickly and accurately, breadth of experience matters enormously. What About Commitment and Culture? The most common objection to fractional CTOs is around commitment. Won't a part-time leader be less invested? Won't they lack the deep connection to the team and culture that a full-time hire brings? These are reasonable concerns, and they deserve a direct answer. First, commitment isn't measured in hours. I've seen full-time CTOs who were mentally checked out by month eight, collecting their salary and vesting their equity while delivering minimal strategic value. I've seen fractional CTOs who transformed a company's technical trajectory in twelve hours a week because every hour was focused, high-leverage, and informed by years of relevant experience. Second, the culture argument often confuses presence with impact. A CTO's cultural contribution isn't about attending every team lunch. It's about setting technical standards, modeling good decision-making, mentoring senior engineers, and creating an environment where the team can do their best work. These are things that can be done in three days a week. They don't require five days of physical presence. Third, consider the cultural cost of the alternative. What's the cultural impact of a 14-month leadership vacuum while you search? What's the cultural impact of a CTO who arrives, reorganizes everything, and leaves after two years? What's the cultural impact of strategic whiplash between successive CTOs with different philosophies? Stability, even part-time stability, often serves culture better than the revolving door of full-time hires. The Real Question The question isn't whether a full-time CTO or a fractional CTO is inherently better. Both models have their place. The real question is which one makes sense at your stage, with your resources, for your specific needs right now. If you're a seed-stage startup with 3 to 10 engineers, burning $150,000 a month, and you're about to spend 14 months and half a million dollars finding a full-time CTO who will stay for 26 months, I'd encourage you to ask whether that's really the best use of your runway. A fractional CTO who starts in two weeks, costs less than half as much, brings cross-company pattern recognition, requires no equity, and can be scaled up or down as your needs evolve might not just be the cheaper option. It might be the smarter one. If you're at Series B with 30+ engineers and a clear need for a full-time technical executive, then by all means, make that hire. But even then, consider using a fractional CTO to bridge the gap during the search, so your technical strategy doesn't stall for 14 months while you find the right person. The math is the math. One is an investment with clear, bounded costs and immediate returns. The other is a gamble with enormous upfront costs, extended timelines, and uncertain tenure. Which one makes more sense at your stage? Final Thoughts If this analysis resonated with you, or if you're currently navigating this exact decision, I'd genuinely like to hear your perspective. Every company's situation is different, and the right answer depends on context that no blog post can fully capture. You can find me on LinkedIn and Threads, where I regularly share thoughts on technical leadership, engineering strategy, and the realities of building technology at scale. If you're a founder weighing this decision, feel free to reach out directly. No pitch, no agenda, just a conversation between people who care about building things the right way. And if you found value in this breakdown, consider sharing it with a founder or CEO in your network who might be about to write a very large check without running the numbers first. Sometimes the most valuable thing you can do for someone is help them see the full picture before they commit. What's your experience been? Have you hired a full-time CTO, worked with a fractional one, or been on the other side of this equation? I'd love to hear what worked, what didn't, and what you'd do differently. Drop me a message or leave a comment below. --- # Architect or Extinct: Why Software Developers Must Evolve Beyond Writing Code URL: https://www.ivanturkovic.com/2026/02/12/architect-or-extinct-software-developers-must-evolve/ Published: 2026-02-12 Topics: AI, Career & Leadership, Software Engineering A house architect does not lay bricks. They do not mix concrete, install plumbing, or wire electrical panels. They design the building. They decide how spaces connect, where light enters, how loads distribute, and how the structure will age over decades of use. The actual construction is performed by skilled tradespeople following the architect's plans. Nobody considers this arrangement strange. Nobody argues that architects should spend their days on scaffolding to prove they understand buildings. The profession evolved past that point generations ago. Software development is approaching the same inflection point. And most developers are not ready for it. For decades, the value of a software developer was measured largely by their ability to write code. How fast they could implement features. How fluently they navigated a framework. How many lines they could produce in a sprint. The implementation itself was the bottleneck, and the people who could push through that bottleneck fastest were the most valuable. That equation is changing fundamentally, and the developers who don't recognize the shift will find themselves in the same position as the stonemason who refused to acknowledge that architects had taken over building design. The Architect Analogy Is Not Hypothetical Consider how the construction industry actually evolved. In ancient times, the person who designed a building was often the same person who built it. Master builders directed construction while participating in the physical work. Over centuries, as structures became more complex and the science of materials, load bearing, and environmental factors advanced, a split occurred. Design became a distinct discipline. Architecture emerged as a profession focused entirely on planning, specification, and oversight rather than manual execution. This did not happen because builders became less skilled. It happened because the complexity of what was being built outgrew what any single person could manage while also doing the physical construction. The knowledge required to design a modern skyscraper, accounting for wind loads, seismic activity, energy efficiency, accessibility regulations, fire safety, and urban integration, is so vast that it demands full cognitive attention. You cannot think about structural dynamics while you are also hanging drywall. Software has reached a similar level of complexity. A modern production system involves distributed architectures, security models, compliance frameworks, data privacy regulations, performance optimization, observability, deployment pipelines, and integration with dozens of external services. The person who understands how all of these pieces fit together, who can design a system that remains maintainable, secure, and scalable over years, is providing fundamentally different value than the person who can implement a single API endpoint quickly. AI is the force that makes this split not just logical but inevitable. What AI Actually Changes AI coding assistants like Claude, Copilot, and their successors are not replacing developers in the way that sensational headlines suggest. What they are doing is commoditizing the implementation layer. The part of software development that involves translating a clear specification into working code is becoming dramatically faster and cheaper. Not perfect, not autonomous, but fast enough and good enough that the implementation itself is no longer the primary constraint. This changes the economics of the profession in a way that mirrors what happened in construction. When you can generate functional code in minutes instead of hours, the value shifts upstream. It moves toward the person who knows what to build, why to build it, how it should fit into the larger system, and what trade-offs are acceptable. In other words, it moves toward the architect. I have spent over twenty years building software systems in fintech, blockchain, and high-traffic environments. I have watched multiple technology shifts reshape what developers do on a daily basis. Frameworks abstracted away boilerplate. Cloud platforms abstracted away infrastructure. Containers abstracted away environment management. Each time, the developers who thrived were not the ones who clung to the lower abstraction layer. They were the ones who moved up, who recognized that the freed cognitive capacity should be invested in higher-order thinking rather than in doing the same low-level work faster. AI is the most significant abstraction shift yet, and the direction it points is clear. The Five Roles Developers Must Grow Into When I say developers need to become architects, I do not mean everyone needs the literal title of "Software Architect." I mean that the skills, perspectives, and responsibilities that define architectural thinking need to become standard expectations for professional software engineers. The developers who thrive in the AI era will inhabit one or more of these evolved roles. 1. The System Designer A house architect decides that a building needs load-bearing walls in specific locations, that the HVAC system should route through particular channels, and that the foundation must be engineered for local soil conditions. A system designer makes equivalent decisions for software. Which services communicate synchronously versus asynchronously? Where do you place consistency boundaries in a distributed system? How do you structure data so that the application can evolve without catastrophic migrations three years from now? AI can generate code for individual components, but it cannot make these systemic decisions with the context required. It does not know your business constraints, your team's capabilities, your regulatory environment, or the specific failure modes that matter most to your users. System design requires holding an enormous amount of context simultaneously and making judgment calls that balance competing priorities. That is precisely the kind of work that becomes more valuable as implementation becomes cheaper. Consider a real example from fintech. When designing a payment processing system, the choice between eventual consistency and strong consistency at various integration points has enormous implications for user experience, regulatory compliance, and operational cost. An AI can implement either approach. It cannot determine which approach is correct for your specific business model, transaction volume patterns, and risk tolerance. That determination requires deep domain knowledge and architectural judgment. 2. The Specification Writer Architects produce detailed blueprints that construction crews follow. The quality of the building depends directly on the quality of those blueprints. Ambiguous drawings produce ambiguous results. Software is experiencing the same dynamic in a concentrated way. AI does not struggle with hard tasks. It struggles with vague ones. When you give an AI coding assistant a crystal-clear specification of what a function should accept, process, and return, including edge cases and error handling expectations, it produces remarkably good code. When you give it a vague instruction like "build a user management system," it produces something that superficially works but makes dozens of assumptions you never validated. The ability to write precise, complete specifications is becoming one of the most valuable skills in software development. This is not prompt engineering in the trendy sense. This is the engineering discipline of defining requirements with enough clarity that any competent executor, human or AI, can produce the correct result. Architects have practiced this discipline for centuries. Software developers are only now being forced to confront how imprecisely they have been specifying their work. Think about how many bugs in production software trace back not to coding errors but to ambiguous requirements. The developer interpreted the ticket one way. The product manager meant something different. The edge case nobody discussed caused the outage. AI amplifies this problem because it executes specifications literally and at scale. If your specification is wrong, you get wrong code faster than ever before. 3. The Quality Arbiter An architect does not build the house, but they inspect the construction. They verify that what was built matches what was designed. They catch deviations before they become structural problems. They enforce standards not through labor but through judgment. As AI generates more code, the need for sophisticated code review intensifies rather than diminishes. AI-generated code can be syntactically correct, pass basic tests, and still contain subtle architectural violations, security vulnerabilities, performance antipatterns, or maintainability problems that only an experienced engineer would recognize. The developer who can read AI-generated code critically, understand its implications within the broader system, and identify where it deviates from the project's standards is providing enormous value. This is analogous to the building inspector who understands structural engineering deeply enough to spot when a contractor has taken shortcuts that will not be apparent for years. The inspection skill requires all the knowledge of construction without requiring the inspector to personally do the construction. In software, the developers who evolve into quality arbiters will need deep technical knowledge precisely because they are evaluating work they did not personally write. I have seen this play out already in teams that adopted AI coding tools aggressively. The teams with strong code review cultures, where senior developers know how to evaluate architectural decisions, security implications, and long-term maintainability, produce excellent results with AI assistance. The teams without that culture produce code faster than ever, but the technical debt accumulates just as quickly. 4. The Domain Translator Architects serve as translators between the people who will use a building and the people who will construct it. A client says, "I want an open, welcoming space for customers." The architect translates that into specific ceiling heights, sight lines, material choices, and lighting designs. Without this translation layer, the builder would have to guess what "open and welcoming" means structurally. Software developers have always needed to translate business requirements into technical implementations, but AI makes this translation skill dramatically more important. When the implementation layer is fast and cheap, the primary constraint becomes how well the business problem is understood and translated into technical terms. The developer who can sit in a meeting with stakeholders, understand the underlying business need (not just the stated feature request), and translate that into a system design that actually solves the problem is worth far more than the developer who can implement a feature spec quickly. In blockchain and fintech, where I have spent much of my career, this translation skill is especially critical. A compliance officer says, "We need to meet the new anti-money laundering requirements." Translating that into specific technical controls, monitoring systems, data retention policies, and audit trails requires someone who understands both the regulatory intent and the technical possibilities. AI cannot bridge that gap. It does not understand the regulator's enforcement priorities, the company's risk appetite, or the practical trade-offs between compliance thoroughness and user friction. 5. The Technical Leader Senior architects do not just design individual buildings. They establish design principles, material standards, and aesthetic guidelines that shape entire developments. They mentor junior architects. They define the visual and structural language that gives a project coherence across multiple buildings and years of construction. Technical leaders in software serve the same function. They establish coding standards, architectural patterns, security practices, and quality benchmarks that keep a codebase coherent as it grows. With AI generating more code faster, this governance function becomes essential. Without strong technical leadership, AI-assisted teams produce a chaotic mixture of styles, patterns, and approaches that becomes unmaintainable within months. The developers who have experience building and leading teams, mentoring junior engineers, conducting meaningful code reviews, and establishing engineering culture have been practicing exactly the skills needed to manage AI-assisted development. Leading a team of junior developers and leading a team of AI agents require the same fundamental capabilities: clear communication, quality standards enforcement, strategic delegation, and the ability to maintain architectural coherence across many parallel streams of work. What Happens to Those Who Don't Evolve This is the uncomfortable part, and I think honesty serves better than false reassurance. Developers whose primary value proposition is implementing well-defined features in a single technology stack face genuine pressure. Not tomorrow, not this quarter, but over the next three to five years. The same way that manual typesetting was replaced by desktop publishing, and hand-drawn animation was replaced by digital tools, the pure implementation role is being compressed. Not eliminated entirely, but compressed to the point where far fewer people are needed for the same volume of output. The historical pattern is instructive. When CAD software replaced hand drafting in architecture, the drafters who evolved into CAD operators survived the transition. The ones who evolved further, into designers who used CAD as a tool for exploring ideas rather than just documenting them, thrived. The ones who insisted that hand drafting was superior and refused to adapt eventually found that the market had moved past them. In software, we are seeing the early signs of this compression. Junior developer hiring has already tightened at many companies, not because companies need fewer features built, but because senior developers with AI tools can handle work that previously required additional junior team members. This is not a temporary market correction. It is a structural shift in how software teams are composed. The developers at greatest risk are those in a specific middle ground: experienced enough to command higher salaries, but primarily valued for implementation speed rather than architectural judgment. If your main contribution is writing code that a clear specification can fully describe, AI is coming directly for your competitive advantage. Real-World Examples of the Shift Let me ground this in specific scenarios I have encountered or observed. The Agency That Halved Its Team A software agency I know restructured from twelve developers and two architects to five senior developer-architects and AI tooling. Their output did not decrease. It actually improved in terms of consistency and quality because the remaining five engineers were all capable of architectural thinking. They design the system, use AI to generate implementation, and then review the output with deep technical understanding. The seven developers who were primarily implementers were not fired in a dramatic layoff. Their contracts simply were not renewed as projects concluded, and new projects were staffed with the leaner, more architecturally skilled team. The Fintech Startup That Changed Its Hiring A fintech company building a payment platform shifted its hiring criteria over the past year. Previously, their technical interviews focused heavily on coding speed and algorithm implementation. Now, the interviews emphasize system design, failure mode analysis, and the ability to articulate trade-offs. Their CTO told me directly: "I can give any competent developer AI tools and they'll write the code. What I cannot easily find is someone who knows why we should use event sourcing for our transaction ledger instead of a simple CRUD model, and who can articulate the implications of that choice for our audit requirements and regulatory compliance." The Senior Developer Who Became a System Designer A backend developer with eight years of experience in Rails applications made a deliberate pivot eighteen months ago. Instead of deepening his framework expertise, he invested time in studying distributed systems, reading architecture decision records from open-source projects, and practicing system design through side projects. He now works as what his company calls a "technical designer," spending most of his time producing architecture documents, API contracts, and integration specifications that a team of developers (assisted by AI) implements. His compensation increased by 40% because the design work he does is harder to replace than the implementation work he used to do. The Construction Parallel in Practice A friend of mine is an actual house architect. When I described the current AI situation in software to him, he laughed and said, "Welcome to what we figured out a hundred years ago." He explained that young architecture students still learn structural engineering, materials science, and construction techniques. They need to understand how buildings are physically built so they can design structures that are actually constructible. But nobody expects them to spend their career pouring foundations. The technical knowledge informs the design. The design is the job. This is precisely where software development needs to go. Understanding how code works, how databases behave under load, how networks fail, and how security vulnerabilities emerge is essential knowledge. But the primary output of a senior software professional should increasingly be design, specification, and oversight rather than implementation. How to Make the Transition If you are a developer reading this and recognizing that your current role is heavily weighted toward implementation, the transition is both possible and necessary. Here is what I have seen work, drawn from my own experience and from watching others navigate similar shifts. Start Designing Before You Are Asked To Do not wait for a promotion to "architect" before you start thinking architecturally. On your current projects, before writing any code, write a brief design document. Describe the approach you plan to take, the alternatives you considered, and the trade-offs you are accepting. Even if nobody asked for this document, the practice develops the muscle. Over time, these documents become valuable artifacts that demonstrate your ability to think beyond implementation. Study Failures, Not Just Successes Architects learn enormously from building failures. Engineers study bridge collapses, not just beautiful bridges. Software developers should do the same. Read post-mortems from major outages. Study cases where architectural decisions led to years of technical debt. Understand why certain systems became unmaintainable. This knowledge is what separates someone who can design a robust system from someone who can only implement features in an existing one. Learn the Business Domain Deeply House architects study how people actually use spaces, not just how to draw floor plans. Software architects need to understand their business domain with the same depth. If you work in fintech, learn how payment networks actually function, what regulatory bodies care about, and how financial risk is managed. If you work in e-commerce, understand supply chain logistics, inventory management economics, and customer acquisition costs. The deeper your domain knowledge, the more valuable your architectural decisions become because they account for business realities that pure technologists miss. Practice Explaining Technical Decisions to Non-Technical People Architects present their designs to clients who do not understand structural engineering. The ability to explain why a design decision matters, in terms the client cares about, is a core professional skill. Software developers who can explain to a product manager why a particular architectural approach will save six months of development next year, or to a CEO why investing in observability now prevents revenue loss later, are providing value that AI cannot replicate. This communication skill is not soft or peripheral. It is central to the architect role. Use AI as Your Construction Crew The best way to develop architectural skills is to practice them. Use AI coding tools to handle the implementation while you focus on design. Approach your next project as if you were the architect: define the structure, specify the components, describe the interfaces, and then let AI build the pieces. Review what it produces with a critical eye. This workflow forces you to think at the design level because you are not getting dopamine from writing the code yourself. It is uncomfortable at first. That discomfort is the growth. Build Cross-Disciplinary Knowledge House architects study art, psychology, urban planning, environmental science, and history alongside structural engineering. They understand that a building exists within a context far larger than its foundation. Software developers need equivalent breadth. Study security not just as a checklist but as a discipline. Understand data privacy not just as GDPR compliance but as an ethical framework. Learn about accessibility not as a requirement but as a design philosophy. This breadth of knowledge is what allows you to make architectural decisions that account for the full context in which your software operates. The Career Paths That Will Thrive Looking at where the industry is heading, several specific career trajectories stand out as particularly resilient and valuable. Platform architects who design the foundational systems on which applications are built will be in sustained high demand. As AI generates more application-level code, the platforms those applications run on need to be designed with greater sophistication. Someone needs to decide how the deployment pipeline works, how services discover each other, how data flows between systems, and how the entire platform remains observable and debuggable. This is pure architectural work. Security architects will become increasingly critical as AI-generated code introduces new attack surface patterns that traditional security reviews may miss. The person who can design security into a system from the ground up, rather than bolting it on after implementation, provides value that scales with the volume of code being produced. Data architects who understand how information flows through complex systems, how privacy is maintained at scale, and how data models should evolve to support changing business needs will find their skills more sought after than ever. AI can generate database schemas, but it cannot determine the right data model for a business that is simultaneously serving customers in jurisdictions with conflicting privacy regulations. Technical product managers who combine engineering depth with product vision will bridge the gap between what the business needs and what the technology can deliver. This is the domain translator role elevated to a career path, and it requires the kind of dual fluency that AI cannot provide. AI orchestration specialists, the topic I have explored in previous writing, will define how AI tools are integrated into development workflows, what guardrails are necessary, and how quality is maintained at scale. This role is essentially the architect of the development process itself. The Uncomfortable Truth About Coding Passion Many developers love writing code. It is genuinely satisfying work. The flow state of solving a complex implementation problem, the elegance of a well-crafted function, the satisfaction of seeing tests go green after a difficult refactor. I understand this deeply because I have felt it for over two decades. But loving the craft of implementation and insisting on doing it yourself when better alternatives exist are two different things. An architect who trained as a carpenter might still love woodworking. They might do it on weekends as a hobby. But they do not insist on personally framing every house they design because their professional value lies in the design, not the framing. The developers who will struggle most with this transition are not the least skilled. They are often the most skilled implementers, the ones whose identity is most tightly bound to their ability to write exceptional code. Letting go of that identity, or rather expanding it to encompass design thinking, quality arbitration, and strategic leadership, requires a psychological shift that technical skill alone cannot accomplish. Your passion for code made you great. The industry needed people who cared deeply about implementation quality, who could see the difference between adequate code and excellent code. That passion does not become worthless. It transforms. The same eye that distinguishes elegant code from clumsy code is the eye that distinguishes robust architecture from brittle architecture. The skill transfers. The scope expands. The Timeline Is Shorter Than You Think If you look at how quickly AI coding tools have improved in just the past two years, extrapolating another three to five years paints a clear picture. The tools that currently assist with individual functions and modules will assist with entire features and subsystems. The review and oversight will still require human judgment, but the volume of code a single human can effectively oversee will increase dramatically. This is not a reason to panic. It is a reason to start transitioning now rather than waiting until the pressure is acute. The developers who begin building architectural skills today will be well-positioned when the market fully adjusts. The ones who wait until AI has clearly commoditized their current role will be competing for a shrinking pool of implementation positions against both AI tools and against the developers who started adapting earlier. In construction, the transition from master builder to architect took centuries. In software, because everything moves faster, it will take a decade at most. We are already several years into that transition. The question is not whether it will happen. The question is whether you will be an architect or a trade that the architect hires on a per-project basis. Architect or Extinct The parallel to house architecture is not a metaphor I am forcing onto the situation. It is a description of a structural change that has already happened in dozens of professions and is now happening in software development. The people who design systems are more valuable than the people who build individual components. This has always been true, but AI is making it undeniable. I do not write this to create anxiety. I write it because the transition is manageable for anyone willing to invest in their own evolution. The technical knowledge you have accumulated is not wasted. It is the foundation on which architectural thinking is built. No architect designs a building without understanding how buildings are constructed. No software architect designs a system without understanding how code behaves in production. Your experience matters. Your implementation skills matter. They just need to serve a different purpose than they did five years ago. The developers who thrive in the next era will not be the ones who write the most code. They will be the ones who design the best systems, write the clearest specifications, maintain the highest quality standards, and translate business needs into technical solutions with the most precision. They will be architects, in every meaningful sense of the word. The only question left is which side of that divide you choose to stand on. Final Words If this resonated with you, I write regularly about how AI is reshaping software development, engineering leadership, and the skills that will matter most in the years ahead. Every post is grounded in over two decades of hands-on experience building production systems, not in speculation or trend chasing. Follow me on LinkedIn for updates and discussions around these topics. You can also find shorter, more conversational takes on Threads. If you have thoughts on this piece, whether you agree, disagree, or see it differently from your own experience, I genuinely want to hear from you. The best insights come from conversations, not monologues. Reach out through my blog at ivanturkovic.com or connect with me directly on LinkedIn. What do you think? Are you already making this transition, or does the idea of stepping back from implementation feel like giving something up? I suspect the answer reveals more about where you are in your career than any technical assessment ever could. --- # Integrate: Completing the ADD Cycle for AI-Driven Development URL: https://www.ivanturkovic.com/2026/02/11/integrate-completing-add-cycle/ Published: 2026-02-11 Topics: AI-Driven Development, Software Engineering Code that passes evaluation is ready for integration. This is the final phase of the ADD cycle, where generated code becomes part of your system. But integration is more than merging a pull request. It is where AI-generated code meets the full reality of your codebase, your testing infrastructure, your deployment pipeline, and your team's practices. Integration also closes a feedback loop. What you learn during integration informs your next specification. The cycle continues, each iteration building on the lessons of the previous one. What Happens After Code Passes Evaluation Evaluation answers the question: "Is this code correct and appropriate?" Integration answers a different question: "Does this code work within the system?" These questions are related but not identical. Code can be correct in isolation and still fail in context. It can pass evaluation and still break when it encounters real data, real load, or real edge cases that neither you nor the AI anticipated. The Integrate phase has five components: testing, documentation, CI/CD validation, system-level review, and technical debt tracking. Each addresses a different aspect of bringing new code into an existing system. Testing AI-Generated Code AI-generated code requires the same testing as human-written code, but the testing focus should account for how AI tends to fail. Test the edge cases explicitly. AI models are trained on examples, and examples tend to show common cases. The happy path is usually well-handled. Edge cases are where AI-generated code most often fails. Empty inputs, maximum sizes, unusual character encodings, concurrent access, resource exhaustion. If your specification listed edge cases, write tests for each. If your specification did not list edge cases, that was a specification gap, and you should note it for future iterations. Test error handling thoroughly. AI often generates optimistic code that handles errors superficially. Test what happens when dependencies fail. Test what happens when external services time out. Test what happens when data is malformed. Verify that errors are caught, logged, and handled gracefully rather than propagating as unhandled exceptions. Test integration points. AI generates code in isolation. It does not see how that code interacts with the rest of your system. Write integration tests that exercise the boundaries: API contracts, database interactions, message queue behavior, file system operations. These tests catch mismatches between what the AI assumed and what your system actually provides. Test with realistic data. AI-generated code is often tested with simple, clean data during development. Production data is messier. Run your tests with data that reflects production reality: varied formats, legacy records, unexpected nulls, strings in multiple languages. Data that worked in the AI's training examples may not match your actual data. Consider property-based testing. For algorithmic code, property-based testing can reveal failures that example-based tests miss. Instead of testing specific inputs and outputs, you define properties that should always hold, and the testing framework generates many random inputs to check those properties. This is particularly valuable for AI-generated code because it explores the input space more thoroughly than hand-written examples. Documentation Updates AI-generated code often comes with AI-generated documentation: comments, docstrings, and sometimes README updates. During the Generate phase, you may have iterated on the code multiple times. The documentation may describe an earlier version rather than the final implementation. Verify comment accuracy. Read every comment in the generated code and verify it describes what the code actually does. Pay particular attention to comments describing edge case handling, error conditions, and performance characteristics. These are the comments most likely to be outdated after iterations. Update architectural documentation. If the generated code introduces new components, patterns, or dependencies, update your architectural documentation to reflect this. The AI does not know about your architecture diagrams, your decision records, or your system documentation. You must update these manually. Document the ADD context. Consider adding a brief note about how this code was generated. Not in the code itself, but in your commit message or PR description. "Generated using ADD with specification X, evaluated against checklist Y." This context helps future developers understand the code's origin and provides a trail back to the specification if questions arise. Generate or update API documentation. If the code exposes APIs, ensure the API documentation is accurate and complete. AI-generated code may follow your API conventions, but the documentation generators that read those conventions need the actual code in place before they can generate accurate docs. CI/CD Validation Your continuous integration pipeline provides automated validation that complements manual evaluation. Let the machines check what machines check well. Run the full test suite. Not just the tests for the new code, but the entire suite. AI-generated code may have unexpected interactions with existing functionality. A full test run catches regressions that focused testing might miss. Enforce code quality gates. Linting, formatting, static analysis, type checking. These automated tools catch issues that humans overlook. If your pipeline includes security scanning, code coverage thresholds, or complexity metrics, let them run against the AI-generated code just as they would against human-written code. Validate deployment requirements. If your code requires new environment variables, new dependencies, new infrastructure, or new permissions, the deployment pipeline should validate that these are in place. AI-generated code sometimes assumes dependencies that do not exist in your environment. Review pipeline failures carefully. When CI fails on AI-generated code, investigate thoroughly. The failure might reveal an issue with the generated code, but it might also reveal a gap in your specification. If the AI produced code that violates a convention enforced by your pipeline, you should have specified that convention. Add it to your context library for future generations. System-Level Review Code that passes all tests and all automated checks can still be wrong at the system level. The Integrate phase includes a step back to consider the broader impact. Consider performance at scale. The code works with test data. Will it work with production data volumes? If you are adding a new database query, how will it perform when the table has millions of rows? If you are adding a new API endpoint, how will it perform under load? System-level thinking catches performance issues that component-level testing misses. Consider failure modes. How does the system behave when this new code fails? Does failure cascade? Does it fail gracefully with clear error messages? Does it trigger appropriate alerts? System resilience depends on understanding how components fail, not just how they succeed. Consider operational impact. Does this code change how the system is monitored, deployed, or maintained? Does it introduce new log messages that operators need to understand? Does it change resource consumption patterns? Operations teams need to know when the system changes in ways that affect them. Consider security surface. Does this code expose new attack surfaces? Does it handle user input in new ways? Does it access sensitive data? Security review at the system level considers how the new code fits into the overall security posture, not just whether the code itself is secure. Consider dependencies. Does this code introduce new dependencies, either explicit (new libraries) or implicit (new services it calls)? Each dependency is a potential point of failure and a potential security risk. System-level review considers whether the dependencies are justified and appropriately managed. Technical Debt Tracking Sometimes you integrate code knowing it is not perfect. Perhaps the specification was incomplete and you discovered the gaps during integration. Perhaps the code works but is not optimally structured. Perhaps time pressure requires shipping now and improving later. This is acceptable, but only if you track it explicitly. Document known limitations. If the code has known edge cases it does not handle, document them. If the code has performance characteristics that may need improvement, document them. Create tickets, add TODO comments with ticket references, or update your technical debt tracking system. Distinguish intentional from accidental. Intentional technical debt is a decision: "We know this is not perfect, but we are shipping it because the tradeoff is acceptable." Accidental technical debt is a surprise: "We did not realize this was a problem." Tracking helps you understand which debt you are taking on deliberately and which you are accumulating without awareness. Set review triggers. For significant technical debt, define when you will revisit it. "Review this when we exceed 10,000 users." "Revisit this before the next major release." Without explicit triggers, technical debt tends to be forgotten until it causes a crisis. Learn from debt patterns. If you consistently integrate code with the same category of technical debt, you have a systemic issue. Perhaps your specifications need to be more comprehensive. Perhaps your evaluation checklist needs new items. Perhaps certain types of generation are not working well for your team. Track the patterns, not just the individual items. The Integration Phase as Learning Opportunity Every integration teaches you something. The question is whether you capture the lesson. What did testing reveal? If your tests caught issues, those issues represent gaps in evaluation. Should your evaluation checklist include items that would have caught these issues earlier? Should your specification templates prompt for information that was missing? What did CI/CD catch? If your pipeline caught issues, those issues represent gaps in your generation context. Should your system prompt include conventions that the AI violated? Should your constraint patterns be more explicit? What did system-level review uncover? If you discovered issues at the system level, those issues represent gaps in your specification process. Should specifications include more context about system integration? Should you add integration considerations to your specification templates? Feed these lessons back into the cycle. Update your specification templates. Update your evaluation checklists. Update your context libraries. Each integration makes the next specification better. When Integration Reveals Evaluation Gaps Sometimes integration fails in ways that evaluation should have caught. The code breaks in production, or causes problems during deployment, or fails in ways that the evaluation process missed. This is painful but valuable. It reveals exactly where your evaluation process needs improvement. Conduct a brief retrospective. What went wrong? What checklist item would have caught it? What specification detail would have prevented it? What context would have helped the AI avoid it? Resist the temptation to blame the AI. The AI generated what your specification and context guided it to generate. The failure is a system failure: specification, generation, evaluation, and integration working together imperfectly. The fix is to improve the system, not to distrust the tool. Completing the Cycle and Starting the Next With integration complete, you have moved from specification to working code in your system. The ADD cycle is done. But it is rarely a single cycle. Complex features require multiple cycles. Each cycle builds on the previous: the code from cycle one becomes context for cycle two. The lessons from cycle one improve the specification for cycle two. Over time, you develop intuition for how to scope cycles. Too large, and generation becomes unreliable. Too small, and overhead dominates. The right size produces code that can be specified clearly, generated reliably, evaluated thoroughly, and integrated smoothly. This judgment comes from practice. The cumulative result is systems built through structured collaboration. Not systems where AI wrote the code and humans watched, but systems where humans specified, guided, evaluated, and integrated while AI generated. The human remains the engineer. The AI is a powerful tool wielded with discipline. Let's Continue the Conversation What has integration taught you about your specifications or evaluations? Where have you discovered gaps that earlier phases should have caught? How do you track technical debt in AI-generated code? Do you treat it differently from debt in human-written code? Share your experience in the comments. Integration is where theory meets reality, and real-world lessons improve everyone's practice. --- # Evaluation Checklists: Building Your Quality Gate for AI Code URL: https://www.ivanturkovic.com/2026/02/10/evaluation-checklists-quality-gate/ Published: 2026-02-10 Topics: AI-Driven Development, Software Engineering In the previous post, I covered the five dimensions of evaluating AI-generated code: correctness, fitness, security, performance, and maintainability. Understanding these dimensions is essential. But understanding is not enough. Under time pressure, even experienced developers skip evaluation steps. They focus on the dimensions they find most interesting or most familiar, and they neglect the others. When different team members evaluate code, they check different things, and nobody notices the gaps until a bug reaches production. Checklists address this problem. They transform evaluation principles into repeatable practice. They ensure that every evaluation covers the same ground, regardless of who performs it or how much time pressure exists. This post provides complete checklists you can use immediately, plus guidance on adapting them to your context and evolving them based on what you learn. Why Checklists Work for Evaluation The case for checklists comes from fields where the cost of missing something is high: aviation, surgery, construction. In these domains, professionals with decades of experience still use checklists because human memory and attention are unreliable under pressure. Software development shares these characteristics. The cost of missing a security vulnerability or a performance problem can be severe. The pressure to ship is constant. And evaluation requires checking many things in sequence, which is exactly the situation where checklists provide the most value. Checklists work because they externalize memory. You do not need to remember what to check. The checklist remembers for you. This frees cognitive resources for the actual evaluation: looking at the code, understanding what it does, and judging whether it is correct. Checklists also create consistency across a team. When everyone uses the same checklist, everyone checks the same things. Gaps that would exist with individual judgment are eliminated. New team members can perform effective evaluations immediately because the checklist encodes what experienced members have learned. Finally, checklists are learnable artifacts. When evaluation misses a bug, you can ask: "What checklist item would have caught this?" The answer becomes a new item, and future evaluations benefit from the lesson learned. Over time, your checklist becomes a record of everything that has ever gone wrong. The Basic Evaluation Checklist This checklist covers all five evaluation dimensions from the previous post. It is designed to be comprehensive enough to catch common issues while remaining short enough to actually use. Fifteen items, each with a specific question to answer. Correctness Specification match. Does every requirement in the specification have corresponding implementation? List each requirement and verify it is addressed. Input handling. Does the code handle all specified input types, including edge cases like empty values, null, maximum sizes, and invalid formats? Output conformance. Does the code produce output in exactly the specified format, type, and structure? Error conditions. Does the code handle all specified error conditions and produce the specified error responses? Logic verification. For complex logic, have you traced through at least one happy path and one error path with concrete values? Fitness Pattern consistency. Does the code follow the same patterns used elsewhere in the codebase for similar functionality? Naming alignment. Do names (variables, functions, classes) follow the conventions established in the existing codebase? Error handling style. Does the code handle errors using the same approach (exceptions, result types, error codes) as the surrounding system? Security Input validation. Is all external input validated before use? Are there any paths where unvalidated input reaches sensitive operations? Output encoding. Is output encoded appropriately for its context (HTML, SQL, URL, JSON)? Sensitive data handling. Does the code avoid logging, exposing in errors, or transmitting sensitive data inappropriately? Performance Algorithmic efficiency. Is the algorithm appropriate for expected input sizes? Are there any O(n²) or worse operations that could become problems at scale? Resource management. Does the code properly acquire and release resources (connections, file handles, memory)? Query efficiency. For code involving database access, are queries efficient? Are N+1 patterns avoided? Maintainability Clarity of intent. Would a developer unfamiliar with this code understand what it does and why? Is complex logic explained? Using the Basic Checklist Work through the checklist in order. For each item, give a clear yes or no. If the answer is no, note the specific issue. If you cannot answer the question because you lack information, that itself is a finding: the code may need more context or documentation. Do not rush. The checklist takes five to ten minutes for a typical code generation. This is not overhead. This is the core of evaluation. The time invested here prevents much larger time investments debugging production issues later. When an item reveals a problem, you have three options: reject the code and regenerate with an improved specification, fix the issue manually, or note it as accepted technical debt with a plan to address it later. The checklist does not make this decision for you. It ensures you see the issue so you can make an informed decision. Security-Focused Checklist Additions For code that handles authentication, authorization, user input, or sensitive data, add these items to the basic checklist. Authentication verification. If the code requires authentication, is the authentication check present and correct? Can it be bypassed? Authorization checks. If the code requires specific permissions, are those permissions verified? Is the check at the right level of granularity? Injection prevention. For code that constructs queries or commands, are parameterized approaches used consistently? Is there any string concatenation with user input? Secret handling. Are secrets (API keys, passwords, tokens) handled appropriately? Are they ever logged, exposed in URLs, or stored insecurely? Cryptographic practices. If encryption or hashing is used, are current recommended algorithms and libraries used? Are there deprecated approaches? Session management. For code involving sessions, are sessions created, validated, and invalidated correctly? Rate limiting awareness. For exposed endpoints, is there awareness of rate limiting needs? Is the code structured to support rate limiting if needed? Error information leakage. Do error messages avoid revealing internal details (stack traces, database schemas, internal paths) to external users? Cross-site protections. For web-facing code, are CSRF protections in place? Are cookies configured with appropriate flags? Dependency security. If the code introduces dependencies, are they from trusted sources? Are there known vulnerabilities in the versions used? Performance-Focused Checklist Additions For code with scaling requirements, high-frequency execution, or resource constraints, add these items. Memory patterns. Does the code avoid loading large datasets entirely into memory? Is streaming or pagination used where appropriate? Caching considerations. Where caching could improve performance, is it implemented? Where caching could cause stale data issues, is it avoided? Concurrency safety. If the code may execute concurrently, are shared resources protected appropriately? Are there race conditions? Database round trips. Is the number of database round trips minimized? Could multiple queries be combined? Index usage. For database queries, will they use indexes effectively? Are there full table scans on large tables? Timeout handling. For external calls, are timeouts configured? Does the code handle timeout gracefully? Resource pooling. For resources like database connections, is connection pooling used appropriately? Lazy loading. Is expensive computation or data loading deferred until actually needed? Domain-Specific Considerations The checklists above are generic. Your domain will have specific concerns that deserve their own checklist items. Financial systems need items about decimal precision, rounding rules, and audit trails. Healthcare systems need items about data privacy regulations and consent tracking. Real-time systems need items about latency budgets and deterministic behavior. To develop domain-specific items, review past bugs and incidents. Each one that reached production is a candidate for a checklist item that would have caught it earlier. Ask: "What question, if we had asked it during evaluation, would have revealed this problem?" Document the origin of domain-specific items. "Added after the 2025 decimal rounding incident" helps future team members understand why the item matters and prevents it from being removed as seemingly unnecessary. Integrating with Existing Code Review If your team already has code review practices, evaluation checklists should complement them rather than replace them. One approach: use the evaluation checklist before submitting code for review. The checklist becomes a self-review step that catches obvious issues before another developer spends time on them. The code review then focuses on higher-level concerns: design decisions, architectural fit, and knowledge sharing. Another approach: incorporate checklist items into your review template. If your team uses pull request templates, add checklist sections that reviewers complete. This makes checklist usage visible and creates accountability. The goal is not additional process but better coverage. If your current reviews already check everything on these lists, you do not need to change anything. But most teams, when they compare their actual review practices to a comprehensive checklist, find gaps they did not realize existed. Evolving Checklists Based on Discovered Issues A checklist is not a static document. It should evolve as you learn. When a bug escapes evaluation and reaches later stages or production, conduct a brief retrospective. Ask: "What checklist item would have caught this?" If no existing item would have caught it, create a new item. If an existing item should have caught it but was overlooked, investigate why. Perhaps the item is too vague. Perhaps it needs an example. Perhaps it needs to be earlier in the list so it gets more attention. When checklist items consistently pass without revealing issues, consider whether they are still necessary. If a certain category of bug never occurs in your AI-generated code, the checklist items addressing it may be obsolete. Keep items that address severe risks even if they rarely trigger, but remove items that address low-risk issues that never occur. When checklist items consistently trigger false positives, refine them. An item that always flags non-issues will train reviewers to ignore it, which defeats the purpose. Narrow the scope, add exceptions, or reword to improve precision. Review your checklist quarterly. Remove items that have become irrelevant. Add items based on recent learnings. Ensure items are still clear and actionable. A well-maintained checklist grows more valuable over time. Checklist Anti-Patterns The exhaustive checklist. Fifty items nobody reads. The intention is thoroughness, but the result is that reviewers skim or skip entirely. Keep your active checklist short enough to actually use. If you have many items, consider splitting into a basic checklist everyone uses and specialized checklists for specific contexts. The vague checklist. Items like "code is secure" or "performance is adequate" that mean different things to different people. Every item should have a specific question that can be answered yes or no. "Is all user input validated before database queries?" is specific. "Code handles input properly" is not. The aspirational checklist. Items that describe ideal practices your team does not actually follow. If the checklist says "comprehensive unit tests exist" but your team does not write comprehensive unit tests for generated code, the item will always be skipped or falsely checked. Checklists should reflect actual practice, not aspirational practice. The ignored checklist. The checklist exists but is not actually used. Perhaps it is not integrated into the workflow. Perhaps it takes too long. Perhaps people do not believe it adds value. An unused checklist provides no benefit. If your checklist is being ignored, find out why and address the root cause. Making Checklists a Team Asset Checklists work best when they are shared and maintained by the team, not imposed by an individual. Store checklists in version control alongside your code. Changes to the checklist should be reviewed like code changes. This creates shared ownership and ensures everyone is working from the same version. Discuss checklist updates in team retrospectives. When someone proposes a new item, discuss whether it belongs. When someone questions an existing item, evaluate whether it still provides value. These discussions reinforce the purpose of each item and build collective understanding. Onboard new team members by walking through the checklist. Each item is an opportunity to explain a past lesson or a current standard. The checklist becomes a teaching tool that accelerates learning. Let's Continue the Conversation What checklist items would you add for your domain? What bugs have escaped your evaluation that a checklist item could have caught? Does your team use evaluation checklists today? If so, what has made them effective or ineffective? Share your experience in the comments. The best checklists are built from collective learning. --- # You Don’t Want a Claude Code Guru URL: https://www.ivanturkovic.com/2026/02/09/you-dont-want-a-claude-code-guru/ Published: 2026-02-09 Topics: AI, AI-Driven Development, Career & Leadership, Software Engineering The job posting practically writes itself these days. "Looking for a senior developer proficient with AI coding tools. Must be comfortable using Claude Code, Cursor, or Copilot to rapidly produce production-ready code. We need someone who can 10x our output." I have seen variations of this everywhere over the past year. Companies scrambling to find someone who can sit in front of an AI assistant and generate mountains of code every single day. The promise is irresistible: plug an AI-fluent engineer into your team and watch features materialize at unprecedented speed. But here is the uncomfortable truth that two decades of building software keeps teaching me, over and over again: that is not actually what you want. And if you think it is, you are about to learn an expensive lesson. The Bottleneck Was Never Typing Speed Think about the last project that failed or stalled at your company. Was the root cause that your developers could not write code fast enough? Almost certainly not. The project stalled because requirements were unclear. Because three departments had conflicting visions for the product. Because someone made an architectural decision in month one that created compounding technical debt by month six. Because nobody truly understood the user's actual problem, so the team built an elegant solution to the wrong question. This pattern has repeated itself across every company, every team, and every technology generation I have worked with. From early startups wrestling with monolithic architectures to fintech platforms processing millions of transactions, the constraint was never the speed of code production. It was always clarity. Clarity about what to build, why it matters, and how it fits into the larger system. The limiting factor of software engineering has always been product vision and organizational friction. Writing code faster does not fix either of those things. If anything, it makes them worse, because now you can build the wrong thing at remarkable speed. AI Amplifies What Already Exists There is a principle I have come to rely on after years of integrating AI tools into real engineering workflows: AI is an amplifier, not a transformer. It takes whatever exists in your organization and makes it louder. If you have clear product thinking, strong architectural foundations, and a team that genuinely understands user problems, AI tools like Claude Code will make that team extraordinarily productive. They will move mountains. But if you have muddy requirements, disconnected stakeholders, and engineers who have never been given the context to understand why they are building what they are building, AI will amplify that chaos. You will get more code, faster, solving problems nobody asked for, in architectures nobody planned, creating dependencies nobody anticipated. The codebase grows. The clarity shrinks. And debugging AI-generated code that was written without architectural intent is a special kind of frustrating, because the code often looks reasonable on the surface while being fundamentally misaligned with the system's actual needs. I have watched this unfold in real time. A team introduces Claude Code with great enthusiasm. Output triples in the first sprint. But by sprint three, integration issues start surfacing. By sprint five, half the velocity gains have been consumed by rework. The AI wrote exactly what it was told to write. The problem was that nobody told it the right things, because nobody in the room had a clear enough picture of the whole system to give the right instructions. The Skills That Actually Matter Now When I look at the engineers who thrive with AI tools, they share a set of characteristics that have nothing to do with prompt engineering or knowing the latest IDE plugin. They are the same characteristics that made great engineers before AI: deep understanding of business domains, the ability to decompose complex problems into well-defined pieces, architectural thinking that considers how components interact over time, and genuine empathy for the end user. What has changed is that these skills now have leverage they never had before. An architect who can clearly define a system boundary, specify the contracts between services, and anticipate the failure modes can now use AI to implement the pieces at extraordinary speed. But the architecture came first. The clarity came first. The AI just executed. This is something I have observed consistently across every domain I have worked in, from blockchain payment systems to high-availability fintech platforms. The complexity was never in writing the code. The complexity was in understanding what the payment flow needed to handle, how the compliance layer intersected with the transaction pipeline, what happened when a third-party API timed out at the worst possible moment, and how the system needed to behave when everything went wrong at once. Those are questions that require deep business understanding, systems thinking, and years of seeing what actually breaks in production. No AI tool, no matter how sophisticated, can substitute for that kind of earned knowledge. Delegation, Not Dictation There is a useful analogy that I keep returning to: working with AI coding tools is much closer to leading a team than it is to writing code yourself. When you manage a team of engineers, you do not dictate every line of code. You set direction. You define the problem clearly. You establish architectural constraints and quality standards. Then you trust your team to execute while you review their work critically. The engineers who struggle with AI tools are often the ones who try to dictate. They write overly specific prompts, trying to control every implementation detail, and then get frustrated when the output does not match their mental model. The engineers who excel are the ones who delegate. They describe the problem, set the boundaries, and then evaluate the result with experienced eyes. This is why team leadership experience translates so directly into AI effectiveness. If you have spent years learning how to communicate intent clearly to other humans, how to review work critically without micromanaging, and how to detect when someone is confidently producing something subtly wrong, you already have the skills that matter most. The AI is just another team member. A very fast, very tireless team member who needs clear direction and careful review. I have found that the ability to spot when AI is confidently wrong is perhaps the most underrated skill in this new landscape. Large language models do not hesitate. They do not say "I'm not sure about this architectural choice." They produce code with the same confidence whether the approach is brilliant or deeply flawed. Catching those moments requires the kind of experience that only comes from having built, broken, and rebuilt real systems over many years. What You Actually Need to Build If you are a founder, a CTO, or an engineering leader trying to figure out how to harness AI effectively, stop looking for a "Claude Code guru." Instead, look for (or develop) people who have these qualities: First, genuine empathy for users. Not the surface-level "we did user research" variety, but the deep kind where the engineer can articulate why a user behaves a certain way, what frustrates them, and what they actually need versus what they say they want. When an engineer with this understanding sits down with an AI tool, every prompt carries intent. Every feature request is grounded in reality. Second, architectural fluency. Someone who can look at a system and see not just the code, but the forces acting on it: traffic patterns, data flows, failure cascades, scaling pressures, and integration boundaries. This is the person who prevents the AI from generating a technically correct but architecturally catastrophic solution. This kind of thinking comes from years of designing systems that had to survive contact with real users, real data volumes, and real business constraints. Third, ownership mindset. The difference between a developer who writes features and an engineer who owns outcomes is enormous, and it becomes even more pronounced with AI tools. When someone truly owns a problem, they do not just generate code. They question whether the feature should exist at all. They consider how it affects the rest of the system. They think about what happens six months from now when requirements change. They use AI to move faster toward a destination they have chosen deliberately, not to generate output for its own sake. Fourth, the courage to say "this is the wrong problem." The most valuable thing an experienced engineer can do in a room full of stakeholders is redirect the conversation. Not just build what was asked for, but challenge whether it should be built at all. AI makes building cheap. That makes choosing what to build the most important decision in the room. And that decision requires business understanding, technical depth, and the communication skills to bring people along. Then, and Only Then, Mountains Move Here is what I have seen work, consistently. You build a team (or find an individual) that understands the business deeply. Someone who has sat in the meetings where strategy is set and also in the trenches where production incidents happen at 2 AM. Someone who can translate between the language of business stakeholders and the language of systems. Someone who has designed architectures that had to evolve over years, not just pass a sprint demo. You give that team real ownership. Not just responsibility for writing code, but ownership of outcomes. Let them talk to users. Let them push back on requirements. Let them make architectural decisions and live with the consequences. Then you hand them Claude Code, or whatever AI tool fits their workflow. And something remarkable happens. The bottleneck dissolves. Not because the AI writes code fast, but because every line of code the AI writes is aimed at the right target. The architecture is sound because someone with experience defined it. The features solve real problems because someone with empathy specified them. The code integrates cleanly because someone with systems thinking anticipated the interactions. That is when mountains actually move. Not when you hire a prompt engineer who can generate 10,000 lines a day, but when you empower someone who knows which 200 lines actually matter. The Uncomfortable Implication There is an uncomfortable implication in all of this that most organizations do not want to face: AI does not solve organizational problems. If your product vision is unclear, AI will build unclear products faster. If your team lacks autonomy, AI will help them execute bad decisions more efficiently. If nobody in your organization truly understands the user, AI will generate plausible-looking features that miss the mark at unprecedented speed. The organizations that will win with AI are the ones that were already doing the hard, unglamorous work of building clear product thinking, strong engineering culture, and genuine user understanding. AI just removes the last remaining friction between a good idea and its implementation. This is why I keep writing about the human side of software engineering, even as the tools become more powerful. Because the tools are never the constraint. The thinking is the constraint. The clarity is the constraint. The willingness to truly understand a problem before rushing to solve it is the constraint. And those are fundamentally human capabilities that no amount of AI sophistication will replace. The Real Question to Ask So the next time you are tempted to hire a "Claude Code guru" or search for someone who can "leverage AI to 10x productivity," pause and ask a different question. Ask: "Do we have someone who truly understands our users, our architecture, and our business? Someone who can see the whole board, not just the next move? Someone who has the experience to know what works in production, not just in demos?" If the answer is yes, give them AI tools and get out of their way. You will be amazed at what happens. If the answer is no, no AI tool in the world will fill that gap. Start there instead. I write about the intersection of AI, software architecture, and engineering leadership at ivanturkovic.com. If these ideas resonate with how you think about building software, I would love to hear your perspective. Follow me for more writing on what actually matters in this new era of AI-assisted development, or reach out directly if you want to continue the conversation. What has been your experience: has AI changed what you build, or just how fast you build it? --- # An Honest Take on Deploying Rails with Kamal: What Works and What Doesn’t URL: https://www.ivanturkovic.com/2026/02/06/honest-take-kamal-rails-deployment/ Published: 2026-02-06 Topics: Ruby on Rails Kamal has been positioned as the answer to a question Rails developers have asked for years: how do you deploy to your own servers without the overhead of Kubernetes or the cost of a managed platform? And in many ways, it delivers. But the gap between what Kamal promises and what it actually requires in a production environment is wider than most tutorials suggest. I have been using Kamal to deploy Rails applications across multiple projects. Some of those deployments were clean and straightforward. Others turned into multi-day debugging sessions that made me question whether I should have just stayed on the platform I was migrating from. This is an honest account of both sides. The Promise: Heroku on Your Own Servers The pitch is compelling. Kamal, built by the team at 37signals, uses Docker containers and SSH to deploy your Rails application to any VPS. It handles zero-downtime deployments, rolling restarts, and SSL certificates through its built-in kamal-proxy. You write a single deploy.yml configuration file, run kamal setup, and your application is live. No Kubernetes manifests. No Terraform files. No $500/month Heroku bills for what amounts to a few Puma processes and a database. For a fresh application deployed to a clean VPS, this promise is largely kept. And that is worth acknowledging before diving into the complications. Where Kamal Genuinely Shines If you are deploying a new Rails application to a fresh server with no existing infrastructure to work around, Kamal is genuinely excellent. The initial setup is minimal. You configure your server IP, your Docker registry credentials, and your application secrets. You run kamal setup and within a few minutes, your application is running in production with SSL certificates provisioned automatically through Let's Encrypt. The zero-downtime deployment model works well. Kamal boots a new container, waits for it to pass health checks, and then switches traffic from the old container to the new one. If the new container fails its health check, the old one keeps running. This is exactly what you want from a deployment tool, and Kamal handles it with very little configuration. Once a deployment pipeline is set up and stable, the day-to-day experience is smooth. Push your code, run kamal deploy (or let your CI do it), and your changes are live in minutes. The integration with CI tools like GitHub Actions is straightforward. A basic workflow that builds, pushes, and deploys on every merge to main takes about twenty lines of YAML to configure. For teams that want to own their infrastructure without hiring a dedicated DevOps engineer, this is a meaningful improvement over what existed before. Kamal also handles multiple applications on a single server surprisingly well. Each application gets its own set of containers, and kamal-proxy routes traffic based on hostnames. For side projects or small applications that do not justify their own dedicated server, this multi-tenant approach is practical and cost-effective. The First Wall: Postgres on the Same Host Most Rails tutorials for Kamal show you how to set up Postgres as a Kamal "accessory," a Docker container running on the same host as your application. This works, but it introduces a problem that is rarely discussed in those tutorials: security. When you define a Postgres accessory in your deploy.yml and expose its port, Docker adds its own iptables rules that bypass your server's firewall. If you are using UFW (which most Ubuntu servers do), you might assume that running ufw deny 5432 will block external access to your database. It will not. Docker operates on the NAT table of iptables, while UFW operates on the filter table. Traffic destined for your Docker container's published port never passes through UFW's rules at all. This is not a Kamal-specific problem. It is a Docker problem that Kamal inherits. But because Kamal's documentation encourages you to run Postgres as an accessory on the same host, you are very likely to encounter it. The solution is to bind the Postgres port to localhost only by configuring the port as 127.0.0.1:5432:5432 instead of 5432:5432 in your accessory configuration. But if you do not know to do this, your database is sitting on the public internet with whatever password you chose, and UFW will happily report that port 5432 is blocked while it absolutely is not. If you want additional protection beyond localhost binding, you need to work with the DOCKER-USER iptables chain directly or use an external firewall provided by your cloud provider. Neither of these approaches is covered in Kamal's documentation, and both require knowledge that goes well beyond what a typical Rails developer is expected to have. The Health Check Trap: Solid Cache and Rack Attack Rails 8 ships with the Solid trifecta: Solid Cache, Solid Queue, and Solid Cable. It also encourages the use of Rack Attack for rate limiting and request throttling. Both of these are sensible defaults for production applications. Both of them can break your Kamal deployment in ways that are not immediately obvious. When Kamal deploys a new container, it hits the /up health check endpoint to verify that the application has booted successfully. If the health check does not return a 200 response within the configured timeout (30 seconds by default), Kamal considers the container unhealthy and rolls back the deployment. The error message you see is: target failed to become healthy within configured timeout. Here is where it gets interesting. If you are using Rack Attack with strict rate limiting or IP-based throttling, the health check requests coming from kamal-proxy might get blocked. The health check runs frequently during deployment (every few seconds), and depending on your Rack Attack configuration, those rapid successive requests from the same internal IP can trigger your throttling rules. Your application is perfectly healthy, but Rack Attack is doing exactly what you told it to do: blocking what looks like suspicious traffic. The fix is to add an exception for the health check path in your Rack Attack configuration. Something like excluding requests to /up from your throttling rules. But you only discover this after a failed deployment, and the error message gives you no indication that Rack Attack is the culprit. The logs show a timeout. The container appears to start. The application boots. But the health check never succeeds. Solid Cache can introduce a similar issue if your cache backend is not yet available during the initial deployment. If your application's boot sequence tries to connect to a cache store that does not exist yet (because the database has not been migrated, or because the Solid Cache tables have not been created), the /up endpoint can fail or hang. Again, the error message is unhelpful, and the root cause is several layers removed from what Kamal reports. The general pattern here is that Kamal's health check system is simple and reliable in isolation, but any middleware, initializer, or framework feature that interferes with the /up endpoint during the critical boot window can cause deployments to fail. You need to audit your entire request pipeline for anything that might affect that single endpoint, and you need to create exceptions for it. This is manageable once you know about it, but discovering it through failed deployments is not a pleasant experience. The Nginx Question: Do Not Try to Combine Them This is the most opinionated advice in this post, and I stand by it: do not try to run nginx alongside kamal-proxy, especially if SSL is involved. The temptation is understandable. Many Rails developers have years of experience with nginx as a reverse proxy. It handles static file serving, request buffering, gzip compression, and SSL termination. You might want nginx in front of your application for features that kamal-proxy does not provide, or because your existing infrastructure already uses nginx for other services on the same server. The problem is that kamal-proxy and nginx both want to own the same ports (80 and 443) and both want to handle SSL termination. You can configure nginx to listen on the standard ports and forward traffic to kamal-proxy on internal ports, but now you have two reverse proxies in series, each with its own SSL configuration, its own header forwarding behavior, and its own opinions about how health checks should work. The X-Forwarded-For and X-Forwarded-Proto headers get mangled. SSL redirects loop. Health checks that work through kamal-proxy fail when they pass through nginx first. The HTTP Origin header mismatches cause Rails' forgery protection to reject legitimate POST requests. I have seen developers spend days trying to make this combination work, including myself. The number of moving parts is simply too high. You are debugging interactions between nginx's proxy_pass configuration, kamal-proxy's TLS settings, Rails' force_ssl behavior, host authorization rules, and Docker's internal networking, all at the same time. Each component works correctly in isolation. Together, they create a debugging nightmare where fixing one issue introduces another. If you need nginx features, my recommendation is to choose one approach and commit to it. Either use kamal-proxy for everything and accept its limitations, or disable kamal-proxy entirely and manage nginx yourself outside of Kamal's lifecycle. Trying to layer them is not worth the complexity. More Ways It Goes Wrong Beyond the major issues, there are several smaller problems that collectively add up to significant debugging time during your first few deployments. The force_ssl redirect loop. Rails production environments enable force_ssl by default. Kamal's health check hits /up over HTTP internally. The health check gets redirected to HTTPS, which kamal-proxy interprets as a failure. Your application is running, SSL is working for actual users, but deployments fail because the internal health check cannot complete. The fix is to exclude /up from SSL redirects in your production configuration, but this is a non-obvious interaction between two reasonable defaults. The host authorization block. Rails 7 and later include host authorization that rejects requests from unexpected hostnames. Kamal's health check may use an IP address or internal Docker hostname that does not match your configured allowed hosts. The fix is another exclusion rule for the /up path, but the error manifests as a generic health check failure with no clear indication of what went wrong. Architecture mismatches. If you develop on an Apple Silicon Mac and deploy to an AMD64 server (which is most VPS providers), you need to configure cross-platform builds. Kamal supports remote builders and multi-architecture builds, but misconfiguring this results in containers that build successfully but crash immediately on the server. The error output is cryptic, and the root cause is not always obvious. The missing CMD instruction. If your Dockerfile does not include an explicit CMD instruction, the base Ruby image defaults to running IRB. Your container starts, passes the Docker health check (because the container is "running"), but kamal-proxy cannot connect to your application because it is sitting in an interactive Ruby console instead of running Puma. The default Rails Dockerfile includes the correct CMD, but if you have customized your Dockerfile, this is an easy thing to accidentally remove. Credentials during the build phase. If you use environment-specific credentials (staging vs. production), the Docker build phase uses the production environment regardless of what you specify in your deploy configuration. This means staging-specific credentials are not available during asset precompilation, which can cause the build to fail with errors that look completely unrelated to credential management. Memory pressure on small VPS instances. Building Docker images is memory-intensive. If your VPS has limited RAM (2GB or less), the build process can push the system into swap, making everything slow enough to trigger deployment timeouts. The container is not unhealthy. The server is just too slow to boot the application within the default timeout window. You can increase the timeout, use a remote builder, or add swap space, but diagnosing this as the root cause requires monitoring server resources during deployment. Accessory boot ordering. Kamal does not guarantee that accessories (like Postgres or Redis) are fully ready before your application container starts. If your application's boot process tries to connect to a database that is still initializing, it will fail. You may need to add retry logic to your entrypoint script or configure health checks that account for accessory startup time. Once It Works, It Really Works Here is the thing that makes all of the above frustrations tolerable: once you have worked through the initial setup issues and your deployment pipeline is stable, Kamal is genuinely pleasant to use. The daily workflow becomes simple. You push code, your CI pipeline runs tests, and if they pass, it deploys. Zero-downtime. Automatic SSL renewal. Rolling restarts. No platform vendor to negotiate with, no surprise bills, no dependency on someone else's infrastructure decisions. Your $10/month VPS runs your application just as reliably as the $200/month platform you migrated from. GitHub Actions integration is particularly smooth. A basic deployment workflow looks roughly like this: check out your code, set up Docker buildx, log into your registry, run kamal deploy. The Kamal team provides a GitHub Action that handles most of the setup. You add your secrets (registry password, Rails master key, server SSH key) to your repository's GitHub Secrets, reference them in your workflow, and you have a fully automated deployment pipeline in under thirty minutes. For teams that have been deploying with Capistrano, the shift to containerized deployments through Kamal removes an entire class of "works on my machine" problems. Your production environment is defined by your Dockerfile, not by whatever packages happen to be installed on the server. New team members do not need to understand the server's history to deploy safely. Who Should Use Kamal Kamal is a strong choice if you are deploying a new Rails application to a fresh server and you are comfortable with Docker basics. It is also a good fit if you are migrating away from expensive managed platforms and want to retain a similar developer experience at a fraction of the cost. If your infrastructure is simple (one or two servers, a single application per server, managed database), the setup process is close to what the documentation promises. It is a harder sell if you have existing infrastructure that includes nginx, custom firewall rules, or services that were not designed to coexist with Docker's networking model. The more your server already does, the more edge cases you will encounter during the initial setup. This is not because Kamal is poorly designed. It is because deploying containerized applications to shared infrastructure is inherently more complex than deploying to a clean slate. If you are considering Kamal, my advice is to start with a clean server and a simple application. Get the deployment pipeline working end to end before adding complexity. Add your database accessory and verify the firewall situation. Configure your health check exclusions before enabling Rack Attack or force_ssl. Layer in complexity one piece at a time, and test each addition with a full deployment cycle. The problems I described in this post are all solvable. They are just much easier to solve when you encounter them one at a time instead of all at once. The Bigger Picture Kamal represents a meaningful step forward for Rails deployment. It brings the convenience of platform-as-a-service to self-hosted infrastructure without requiring deep DevOps expertise. The rough edges I have described are real, but they are the kind of problems that get solved through better documentation, community knowledge sharing, and incremental improvements to the tool itself. The Rails community has always valued convention over configuration. Kamal extends that philosophy to deployment, and for the most common case (a new application on a clean server), it succeeds. The challenge is that production infrastructure is rarely the most common case. Real servers have history, existing services, security requirements, and operational constraints that no deployment tool can fully anticipate. What Kamal does well is give you a foundation. What it requires is the understanding to adapt that foundation to your specific situation. And that understanding, as with most things in software engineering, comes from deploying, breaking things, debugging, and learning the hard way why certain defaults exist. The Entrypoint File: Where Your Custom Database Setup Lives or Dies Every Rails application deployed with Kamal runs through a file called bin/docker-entrypoint. This is the script that executes before your application server starts inside the container. The default version that ships with Rails is deceptively simple: #!/bin/bash -e # If running the rails server then create or migrate existing database if [ "${*}" == "./bin/rails server" ]; then ./bin/rails db:prepare fi exec "${@}" This checks whether the container is starting the Rails server (as opposed to a background worker or console session), and if so, runs db:prepare to handle schema loading or pending migrations. For a standard Rails application with a straightforward schema, this works perfectly. It stops working the moment you introduce anything that db:prepare does not handle on its own. A concrete example: suppose your application uses a custom Postgres function for full-text search or data transformation. You have defined it in a migration, and your schema references it. When Kamal deploys for the first time and db:prepare tries to load the schema, it fails because the function does not exist yet. The schema expects it. The migration that creates it has not run. You are in a circular dependency, and the deployment fails with an error about an unknown function that tells you nothing about the entrypoint being the actual problem. The fix requires modifying bin/docker-entrypoint to create the function before db:prepare runs: #!/bin/bash -e # If running the rails server then create or migrate existing database if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then ./bin/rails db:create 2>/dev/null || true ./bin/rails db:create_functions ./bin/rails db:prepare fi exec "${@}" The sequence matters. First, db:create ensures the database exists (silencing the error if it already does). Then a custom rake task creates the Postgres functions that the schema depends on. Only then does db:prepare run, finding everything it needs in place. Notice also the change in the condition check. The default entrypoint uses ${*} for string comparison, but if you are running through Thruster (which Rails 8 defaults to with ./bin/thrust ./bin/rails server), the full command string changes and the condition silently fails. Your migrations never run. The application boots with an empty or outdated database, and you get cryptic errors in production that work fine in development. Using positional parameter checks (${@: -2:1} and ${@: -1:1}) catches the Rails server command regardless of what wraps it. This pattern extends beyond custom functions. Any application that needs seed data, extension creation (CREATE EXTENSION for pgcrypto, uuid-ossp, or pg_trgm), or multi-database setup for the Solid trifecta needs a customized entrypoint. The default is a starting point, not a complete solution. Treat it as code that you own and modify as your application's requirements evolve. Aliases: The Small Configuration That Saves Hours One of Kamal 2's best quality-of-life features is aliases, and I am surprised how many deployment guides skip over them entirely. Without aliases, connecting to a Rails console on your production server requires typing: kamal app exec --interactive --reuse "bin/rails console". That is a lot of keystrokes for something you will do multiple times a day when debugging production issues. Add the following to your deploy.yml and save yourself the trouble: aliases: console: app exec --interactive --reuse "bin/rails console" shell: app exec --interactive --reuse "bash" logs: app logs -f dbc: app exec --interactive --reuse "bin/rails dbconsole" Now kamal console drops you into a Rails console. kamal shell gives you a bash session inside the running container. kamal logs streams your application logs. kamal dbc opens a direct database console. These are the commands you will reach for at 2 AM when something is broken in production, and the difference between a quick alias and a long command string matters more than you think when you are under pressure. If you are running staging and production as separate deploy files (deploy.staging.yml and deploy.production.yml), remember that aliases are scoped to the deploy file. Running kamal console -d staging uses the aliases defined in your staging configuration. Define them in both files. Staging and Production: Separate Deploy Files Done Right Speaking of environments, most real projects need at least staging and production deployments, and Kamal handles this through destination-specific deploy files. The naming convention is deploy.staging.yml and deploy.production.yml, invoked with the -d flag: kamal deploy -d staging. What catches people off guard is that these files are not full overrides of the base deploy.yml. They merge with it. Your base file contains shared configuration (registry credentials, builder settings, aliases), and the destination files contain environment-specific values (server IPs, domain names, environment variables). If you duplicate everything in each file, you end up maintaining three copies of the same configuration and introducing drift between them. There is a subtlety with credentials that trips people up during staging deployments. Rails credentials are environment-specific, but the Docker build phase always uses the production environment. If your staging configuration references credentials that only exist in your staging credentials file, the asset precompilation step during the build will fail. You need to either structure your credentials to be available in the production context during build, or use SECRET_KEY_BASE_DUMMY=1 to bypass credential loading during asset compilation. The error messages when this goes wrong reference missing keys or nil values deep in initializer chains, giving no hint that the build environment is the actual problem. Another common mistake is using the same service name across environments. If you deploy both staging and production to the same server (which is common for small projects), the service name must be unique per environment. Otherwise, Kamal treats them as the same application and your staging deployment will replace your production containers. Name them something like myapp-staging and myapp-production and save yourself from an unpleasant surprise. Secrets Management: What Actually Happens to Your Credentials Kamal 2 introduced a dedicated .kamal/secrets file that replaced the older .env approach. This file is committed to version control, but it does not contain your actual secrets. Instead, it contains references to where the secrets come from: environment variables, files, or external secret managers. A typical secrets file looks like this: KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY=$(cat config/master.key) POSTGRES_PASSWORD=$POSTGRES_PASSWORD The important thing to understand is what happens to these values. They get written to an environment file on your server at .kamal/apps/your-app/env/roles/web.env and injected into your Docker containers as plain text environment variables. Anyone with SSH access to your server can read them. Anyone who can exec into your running container can see them with env or printenv. This is not a flaw in Kamal specifically. It is how Docker environment variables work. But it is worth understanding that Kamal's secrets management provides organizational convenience, not encryption at rest. If your compliance requirements demand encrypted secrets management, you need an external tool like HashiCorp Vault, AWS Secrets Manager, or 1Password's integration with Kamal's secrets adapter. A practical tip: after any change to your secrets, you need to run kamal env push to update the values on your server. Forgetting this step after rotating a credential or changing a database password is a reliable way to cause your next deployment to fail. The new container starts with the updated secrets from your local machine, but the environment file on the server still has the old values. The error manifests as authentication failures that work locally but fail in production. Background Workers: Sidekiq, Solid Queue, and the Role Configuration Most production Rails applications need background job processing. Whether you are using Sidekiq with Redis or Solid Queue with the database, Kamal handles this through server roles. You define a separate role for your worker process that uses the same Docker image but runs a different command: servers: web: hosts: - 192.168.1.10 job: hosts: - 192.168.1.10 cmd: bundle exec sidekiq -C config/sidekiq.yml If you are using Rails 8's Solid Queue with the SOLID_QUEUE_IN_PUMA option, you can skip the separate role entirely and run the queue processor inside your Puma server process. This simplifies the deployment at the cost of sharing resources between web requests and background jobs. For small applications and side projects, this trade-off is usually acceptable. For anything processing significant job volumes or long-running tasks, a separate worker role is the better choice. If you use Sidekiq, you need Redis. Adding Redis as an accessory follows the same pattern as Postgres, with the same firewall caveats. Bind it to localhost, set a password, and make sure your REDIS_URL includes the password and uses the Docker network hostname (which follows the pattern your-service-name-redis). The URL format becomes redis://:yourpassword@myapp-redis:6379/0. Note the colon before the password with no username. Getting this URL format wrong results in connection failures that work in development (where Redis typically has no password) but fail in production. One more detail about worker roles: Kamal runs migrations only when starting the web role, not the job role. This is because the entrypoint script's condition checks for the Rails server command. Your worker container boots after the web container has already prepared the database. If you deploy to separate servers for web and job roles, be aware that the job server will pull and start the new container without running migrations. The web server handles that. If your web deployment fails but your job deployment succeeds, you could end up with workers running new code against an old database schema. Log Management: What Kamal Does Not Do for You Kamal gives you kamal app logs to tail your application output, and it configures Docker's log driver with a default max size of 10MB. For debugging, this is sufficient. For production operations, it is not. Docker's default JSON file log driver keeps logs on the host filesystem. Without rotation configuration, these logs grow until they fill your disk. Kamal sets a max-size option, but you should verify this is appropriate for your application's log volume. High-traffic applications can generate 10MB of logs quickly, and when the log file rotates, you lose the older entries. If you need log retention for compliance or debugging historical issues, you need to ship logs to an external service. You can adjust the log configuration in your deploy.yml: logging: options: max-size: 100m Make sure your Rails application is configured with RAILS_LOG_TO_STDOUT=true in production. Without this, Rails writes logs to files inside the container, which are ephemeral and disappear when the container is replaced during the next deployment. Docker captures stdout and stderr, so logging to stdout is not just a preference in containerized environments. It is a requirement for your logs to be accessible at all. Running One-Off Tasks: The Commands You Will Need Beyond the aliases for console and shell access, there are several one-off operations you will need during the lifetime of your deployment. Running a specific rake task: kamal app exec "bin/rails some:task". This starts a new container, runs the task, and exits. If you need an interactive session (anything that reads from stdin), add the --interactive flag. Checking your full resolved configuration including secrets: kamal config. This is invaluable when debugging why an environment variable is not being set correctly. It shows you the merged result of your deploy file, destination file, and secrets. Viewing details about all running containers: kamal details. This shows you which version is deployed, which containers are running, and the state of your accessories. Rolling back a bad deployment: kamal rollback VERSION. You can find the available versions from your container registry or from the output of previous deployments. Kamal will switch traffic back to the specified version without rebuilding anything. Cleaning up old Docker images that accumulate on your server: kamal server exec --all "docker system prune -af". Deployed containers and images accumulate over time. On a small VPS with limited disk space, this becomes a real problem after a few dozen deployments. Schedule this periodically or add it to your deployment workflow. Aliases Revisited: Operational Commands Deserve Shortcuts Too Earlier I showed console, shell, and log aliases. But if you are adding production rake tasks (and you should), those deserve aliases too. Here is the expanded aliases block I use in the base deploy.yml: aliases: # Interactive sessions console: app exec --interactive --reuse "bin/rails console" shell: app exec --interactive --reuse "bash" dbc: app exec --interactive --reuse "bin/rails dbconsole" # Logs logs: app logs -f # Deployment verification verify: app exec --roles=web "bin/rails deploy:verify" # Database operations db-health: app exec --roles=web "bin/rails db:health" integrity: app exec --roles=web "bin/rails integrity:check" # Operational tasks clear-cache: app exec --roles=web "bin/rails ops:clear_cache" env-check: app exec --roles=web "bin/rails ops:env_check" pending: app exec --roles=web "bin/rails ops:pending_migrations" warm-cache: app exec --roles=web "bin/rails ops:warm_cache" Notice that every operational alias bakes in --roles=web. This is the entire point. You define the safety constraint once in the alias, and every invocation inherits it. Nobody on your team needs to remember to add the flag. The alias does it for them. Now your post-deployment check becomes: kamal verify -d production kamal db-health -d production kamal integrity -d production kamal clear-cache -d production kamal env-check -d production kamal pending -d production Compare that to typing kamal app exec -d production --roles=web "bin/rails deploy:verify" six times. Under pressure at 2 AM, kamal verify -d production is what you want to type. Alias Inheritance Across Environments A question that comes up often: do I need to copy these aliases into deploy.staging.yml and deploy.production.yml? No. Aliases defined in the base deploy.yml are inherited by all destination files. The destination-specific files merge with the base configuration, they do not replace it. Your staging and production environments both get every alias you define in the base file. The -d flag tells Kamal which destination to target, and the alias resolves from the merged configuration. Running kamal verify -d staging uses the same alias definition but executes against your staging servers. Running kamal verify -d production targets production. One definition, every environment. The only reason to override an alias in a destination file is if you need genuinely different behavior per environment. For example, if your staging deployment runs on a server where you also want to alias a database reset command that you would never want available in production: # deploy.staging.yml only aliases: reset-db: app exec --roles=web "bin/rails db:drop db:create db:schema:load" This alias exists only in staging. Production does not inherit it because it is defined in the destination file, not the base. Keep destructive shortcuts out of production configurations. The merge behavior works in your favor here. Role-Scoped Commands: Why Every Exec Needs a Target Here is a mistake that bites people once and never again: running a one-off command without specifying a role. When you execute kamal app exec "bin/rails db:schema:load db:seed" without role targeting, Kamal runs that command on every role concurrently. If you have a web role and a job role, both containers will attempt to load the schema and seed the database at the same time. Two simultaneous db:schema:load operations against the same database produce unpredictable results. Table creation conflicts, unique constraint violations on seed data, and partial schema states that leave you unsure which run completed and which failed halfway through. Always scope your commands to a single role: kamal app exec -d production --roles=web "bin/rails db:schema:load" The --roles=web flag ensures the command runs only on the web container. This applies to anything that modifies shared state: migrations, schema operations, data corrections, cache clearing, and any custom rake task that writes to the database. Read-only operations like generating a report or checking record counts are safer across roles, but the habit of always specifying --roles will prevent the mistake that matters. If you are running multiple web servers behind a load balancer, the same principle applies. A data migration running concurrently on three web containers creates three competing writes. Scope it to one host or one role instance. The few extra characters in the command save you from debugging race conditions in production data. Production Rake Tasks You Should Have From Day One Seeding is a development concern. In production, you need operational tasks: things that help you verify, diagnose, and maintain your application without opening a Rails console and typing commands from memory under pressure. Here are the rake tasks I add to every production Rails application deployed with Kamal, and the reasoning behind each one. Deployment Verification After every deployment, you want a quick way to confirm the application is running correctly beyond what the health check endpoint tells you. A health check says "the process is alive." A verification task says "the application is functional." # lib/tasks/deploy.rake namespace :deploy do desc "Verify deployment health beyond basic health check" task verify: :environment do checks = [] # Database connectivity and basic query begin count = ActiveRecord::Base.connection.execute("SELECT 1").first checks e checks --- # The New Bottleneck: Why Clarity Matters More Than Code URL: https://www.ivanturkovic.com/2026/02/05/the-new-bottleneck-clarity-over-code/ Published: 2026-02-05 Topics: AI, AI-Driven Development For two decades, the fastest engineers were the ones who could write code quickly. They knew the shortcuts, the patterns, the frameworks. Their fingers moved faster than their competitors. That era is ending. The new bottleneck isn't your typing speed or your syntax recall. It's your clarity. I've spent twenty years building software, leading teams, and watching the industry evolve through multiple technological shifts. I've seen developers panic over every new abstraction layer, from ORMs to cloud platforms to containerization. Each time, the prediction was the same: this will eliminate the need for real programmers. Each time, the opposite happened. The technology created demand for more sophisticated engineering, not less. But something genuinely different is happening with AI coding assistants. Not because they're replacing programmers. They're not. But because they're shifting where the bottleneck sits in the development process. And most engineers haven't adjusted yet. The Old Bottleneck: Implementation Speed Think about how software development worked before AI assistants became capable. You had a task. You understood what needed to be built. The constraint was how quickly you could translate that understanding into working code. This created a specific set of valuable skills. Typing speed mattered. Syntax fluency mattered. Knowing framework APIs by heart mattered. The developer who could write a React component from memory in three minutes had a real advantage over the one who needed to look up the lifecycle methods every time. We optimized for this bottleneck. We created snippets, templates, boilerplate generators. We memorized keyboard shortcuts. We practiced algorithms until we could implement them without thinking. The measure of a productive day was lines of code written, features shipped, pull requests merged. This wasn't wrong. When implementation is the bottleneck, optimizing implementation makes sense. But bottlenecks move. And when they move, the skills that made you effective can become the habits that slow you down. The New Bottleneck: Specification Clarity AI coding assistants are remarkably good at implementation. Give Claude or Copilot a clear specification, and it will produce working code faster than any human. Not always perfect code, but functional code that serves as an excellent starting point. This shifts the constraint. The bottleneck is no longer "how fast can I write this code?" It's "how clearly can I specify what this code should do?" This sounds like a minor distinction. It's not. These are fundamentally different skills, and most engineers are much better at the first than the second. Implementation is concrete. You're translating a mental model into syntax. The feedback loop is immediate. The code either runs or it doesn't. The tests either pass or they don't. You know within seconds whether you're on the right track. Specification is abstract. You're articulating requirements before you have the feedback of working code. You're making decisions about edge cases you haven't encountered yet. You're defining "done" before you've started. This requires a different kind of thinking, one that many engineers have never had to develop because they could always figure things out during implementation. Why AI Struggles With Vague, Not Hard There's a common misconception that AI assistants struggle with complex tasks. They don't. AI can handle remarkably sophisticated implementations when the requirements are clear. I've watched Claude architect multi-service systems, implement complex state machines, and refactor legacy codebases with nuanced understanding of the business logic. What AI struggles with is ambiguity. Not complexity. Ambiguity. Ask AI to "build a user authentication system," and you'll get something generic that may or may not fit your actual needs. Ask it to "build a user authentication system with email/password and OAuth support, session-based tokens with 24-hour expiry, rate limiting on failed attempts with exponential backoff starting at 1 second, and audit logging of all authentication events to a separate security database," and you'll get something remarkably close to what you actually need. The difference isn't the difficulty of the task. Session management and rate limiting aren't harder than basic authentication. The difference is the clarity of the specification. The second prompt eliminates ambiguity. It defines what "done" looks like before implementation begins. This is where many engineers get frustrated with AI tools. They provide vague instructions, receive generic output, and conclude that AI isn't ready for real work. But the problem isn't the AI's capability. It's the specification's clarity. The AI is doing exactly what you'd expect from a capable junior developer given the same instructions: making reasonable assumptions about the parts you didn't specify. The Clarity Stack Clarity operates at multiple levels, and each level requires different skills to articulate well. I think of this as a stack, similar to how we think about technology layers. Business clarity is the top layer. What problem are we solving? For whom? What does success look like from the user's perspective? Many engineers skip this level entirely, assuming it's someone else's job. But if you can't articulate the business context, your technical specifications will have gaps you don't even recognize. Functional clarity is the next layer. What should the system do? What are the inputs and outputs? What are the state transitions? What are the edge cases? This is where most specifications live, and where most ambiguity hides. "The user can upload a file" leaves enormous room for interpretation. What file types? What size limits? What happens on failure? Where is it stored? Who can access it later? Technical clarity is the implementation layer. What patterns should we use? What libraries? What architectural constraints exist? What performance requirements? This is where engineers are typically most comfortable, but even here, unstated assumptions create ambiguity. "Use a cache" doesn't specify TTL, invalidation strategy, or failure behavior. Quality clarity is the verification layer. How do we know it works? What test coverage is required? What error handling is expected? What logging and observability? This layer is often implicit, left to the implementer's judgment. But judgment varies, and AI doesn't share your implicit standards. Effective AI delegation requires clarity at all four levels. Not necessarily in a formal document, but in your own thinking. If you can't articulate what you want at each layer, you'll spend more time correcting AI output than you would have spent writing the code yourself. The Psychology of Letting Go Here's where it gets uncomfortable. The engineers who struggle most with AI delegation aren't the ones who lack technical skill. They're the ones who are most attached to the craft of coding itself. I include myself in this category. I love writing code. I find satisfaction in elegant implementations, in refactoring messy functions into clean ones, in the flow state that comes from solving a tricky problem through pure implementation. This passion made me a better engineer for twenty years. Now it's the thing that slows me down. When I watch myself resist AI delegation, the pattern is predictable. I tell myself I can write it faster than I can explain it. I tell myself the AI won't get the nuances right. I tell myself I need to understand the implementation deeply to maintain it later. These all sound reasonable. They're all partially true. And they're all rationalizations for the real reason: I want to write the code myself. This is the passion paradox. The same drive that made you excellent at implementation can prevent you from delegating implementation. The joy you find in coding can make you reluctant to let someone else, or something else, do the coding for you. I've seen this pattern before, outside of AI. Senior engineers who can't delegate to junior developers because they know they could do it better. Tech leads who rewrite their team's code instead of providing feedback. CTOs who spend their evenings coding features instead of setting technical direction. The attachment to implementation is a common failure mode in technical leadership. AI makes this failure mode more expensive. When delegation was limited to human team members, the implementation gap was smaller. A senior engineer might be 2x faster than a junior. Now the gap is larger. Clear delegation to AI can be 10x faster than manual implementation for well-specified tasks. Refusing to delegate isn't just a minor inefficiency. It's a significant productivity constraint. The Decision Framework: Stay or Delegate Not everything should be delegated to AI. The skill isn't in delegating everything. It's in recognizing which tasks belong in which category and making that decision quickly. I use a simple framework based on two dimensions: clarity and exploration. Clear task, known solution: Delegate to AI. You know what you want. You know what "done" looks like. The implementation pattern is established. This is where AI excels. Write the specification, delegate the implementation, review the output. Examples: standard CRUD operations, common UI components, well-defined API endpoints, routine refactoring, test generation for existing code. Clear task, unknown solution: Collaborate with AI. You know what you want, but you're not sure how to achieve it. This is where AI becomes a thinking partner rather than an implementer. Explore options together. Ask for multiple approaches. Evaluate trade-offs. The AI handles the research and prototyping while you provide judgment and context. Examples: performance optimization without clear bottleneck, architectural decisions with multiple valid approaches, integration with unfamiliar systems. Vague task, known solution: Clarify first, then delegate. You know how to implement something, but you're not sure exactly what should be implemented. Stop. Don't start coding, whether manually or through AI. Invest time in clarification. Talk to stakeholders. Write acceptance criteria. Define edge cases. Only then delegate. The temptation is to start implementing and figure out the requirements as you go. Resist this. Clarification time is never wasted. Vague task, unknown solution: Stay in the loop. This is exploration territory. You're learning what you want and how to achieve it simultaneously. AI can help, but you need tight feedback loops and hands-on experimentation. Don't try to specify something you don't understand yet. Prototype manually. Get your hands dirty. Once clarity emerges, shift to collaboration or delegation. The key insight is that movement between quadrants is often more valuable than time spent in any single quadrant. Vague tasks should become clear tasks. Unknown solutions should become known solutions. Every hour spent in the exploration quadrant should be an investment toward reaching the delegation quadrant faster. What "Done" Looks Like: The Specification Discipline The most valuable habit I've developed for AI-augmented development is defining "done" before I start. Not after. Not during. Before. This sounds obvious. It's not how most engineers actually work. We typically have a fuzzy idea of what we're building, start implementing, and refine our understanding as we go. The code becomes the specification. We know what we want because we can see what we built. This approach doesn't work well with AI delegation. If you don't know what "done" looks like, you can't evaluate whether the AI's output is correct. You end up in an endless loop of revisions, each one clarifying requirements you should have specified upfront. I've started writing what I call completion criteria before any significant implementation task. These aren't formal requirements documents. They're simple checklists that answer the question: "How will I know this is done?" A completion criteria checklist might look like this: User can upload files up to 50MB in jpg, png, or pdf format Files are validated for type and size before upload begins Upload progress is shown with a progress bar Failed uploads show specific error messages: "File too large," "Invalid file type," "Upload failed, please retry" Successful uploads redirect to the file detail page Files are stored in S3 with private access File metadata is stored in the files table with user_id, filename, size, type, s3_key, and created_at Unit tests cover validation logic Integration test covers the complete upload flow This takes five minutes to write. It saves hours of iteration. And it makes AI delegation dramatically more effective because you can include the completion criteria in your prompt and evaluate the output against specific criteria rather than a vague sense of "not quite right." The Review Skill: Evaluating AI Output Delegation isn't abandonment. Effective AI delegation requires effective AI review. And reviewing code you didn't write requires different skills than writing code yourself. When I write code, I build understanding incrementally. Each line I write adds to my mental model. By the time I'm done, I understand the implementation deeply because I constructed it piece by piece. When I review AI-generated code, I don't have that accumulated understanding. I'm looking at a complete implementation and trying to verify its correctness without having built it myself. This is harder than it sounds, and it's where many engineers make mistakes. The most common mistake is shallow review: scanning the code, checking that it looks reasonable, and accepting it. This is dangerous because AI-generated code often looks reasonable while containing subtle bugs, edge cases, or architectural issues that only become apparent in production. The second most common mistake is overly deep review: reading every line, understanding every decision, mentally executing the code step by step. This is safe but inefficient. If you're reviewing at this depth, you might as well write the code yourself. Effective AI review is strategic. You focus attention on the high-risk areas: boundary conditions, error handling, security implications, performance characteristics, integration points. You verify that the implementation matches your completion criteria. You run the tests, not just read them. You test edge cases manually, not just assume they work. This is a skill that improves with practice. Over time, you develop intuition for where AI tends to make mistakes in your specific domain. You learn what to trust and what to verify. You become faster at review without sacrificing quality. The Compound Effect: Clarity Builds on Clarity There's a compounding benefit to developing specification clarity. The clearer you become at articulating requirements, the clearer your thinking becomes in general. Vague specifications usually indicate vague thinking. When I can't clearly specify what I want, it's often because I haven't actually thought through what I want. The discipline of writing completion criteria forces me to confront ambiguity I would otherwise ignore. This clarity compounds across the development process. Clear specifications lead to clearer implementations. Clearer implementations lead to clearer reviews. Clearer reviews lead to clearer feedback. Each iteration builds on the last. I've noticed that engineers who become good at AI delegation also become better at human communication. The same skills that help you specify tasks for AI help you specify tasks for team members, write clearer documentation, and communicate more effectively with stakeholders. Clarity is a general-purpose skill that happens to be essential for AI delegation. The Speed Paradox Here's an uncomfortable truth: the engineers who are fastest at implementation are often the slowest to adopt AI delegation effectively. When you can write code quickly, the perceived cost of delegation is high. You think: "I could finish this in 20 minutes. Writing a clear specification would take 10 minutes, and then I'd still need to review the output. Manual implementation is faster." This calculation is often wrong, but it feels right because you're comparing the visible cost of specification against the invisible cost of implementation. The 20 minutes of implementation feels fast because you're in flow. The 10 minutes of specification feels slow because you're thinking instead of doing. The calculation changes when you consider cumulative time. Yes, this specific task might be faster to implement manually. But what about the next hundred tasks? A thousand? At some point, the investment in specification clarity pays compound returns. Each specification teaches you to think more clearly. Each delegation frees time for higher-leverage work. Each review improves your ability to evaluate code quality. The engineers who adopt AI delegation fastest are often not the fastest implementers. They're the ones who already valued clarity over speed, specification over implementation, thinking over doing. AI didn't change their approach. It amplified it. What This Means for Your Career The shift from implementation bottleneck to clarity bottleneck has career implications that most engineers haven't fully processed. If your value proposition is "I can write code fast," you're in a vulnerable position. Not because AI will replace you, but because AI will commoditize the skill that differentiates you. Speed of implementation will become table stakes rather than a competitive advantage. If your value proposition is "I can understand complex problems and specify clear solutions," you're in a strong position. This skill is becoming more valuable as implementation becomes easier. The ability to translate vague business needs into clear technical specifications is a bottleneck that AI makes more expensive, not less. The career path this suggests is familiar: move from implementation toward architecture, from coding toward design, from doing toward thinking. But the timeline is compressed. Skills that took twenty years to become essential are becoming essential in five. The transition that used to happen gradually across a career now needs to happen deliberately within a few years. This doesn't mean abandoning implementation entirely. The best architects still write code. The best technical leaders still get their hands dirty. But the ratio shifts. Time spent implementing decreases. Time spent specifying, reviewing, and directing increases. The role becomes more like an orchestra conductor and less like a solo performer. The Resistance Is Natural If you're feeling resistance to this shift, you're not alone. I feel it too. The resistance is natural because it touches something deeper than professional skills. It touches identity. Many of us became engineers because we love building things with code. The act of implementation isn't just a means to an end. It's intrinsically satisfying. It's part of who we are. Being told to delegate that satisfaction to an AI feels like being told to give up part of ourselves. I don't think you have to give it up entirely. There's still room for implementation joy, for flow states, for the craft of coding. But the proportion changes. The occasions change. Implementation becomes something you choose for specific purposes rather than the default approach to every problem. The engineers who navigate this transition successfully will find new sources of satisfaction. The satisfaction of solving harder problems because AI handles the routine ones. The satisfaction of building larger systems because implementation isn't the constraint. The satisfaction of thinking clearly and seeing that clarity manifest in working software without having to type every line yourself. These satisfactions are real. They're just different. The transition requires letting go of one kind of joy to make room for another. The Path Forward If you want to develop the clarity that AI-augmented development requires, here's where I'd start: Practice writing completion criteria. Before your next implementation task, take five minutes to write down exactly what "done" looks like. Be specific. Include edge cases. Include quality requirements. Then implement, whether manually or through AI, and evaluate against your criteria. Notice where your criteria were incomplete or ambiguous. Delegate something you could do faster yourself. Pick a task where manual implementation would be quicker than delegation. Delegate it anyway. Pay attention to where you struggle to specify clearly. Pay attention to what the AI gets wrong and why. The goal isn't efficiency on this specific task. It's learning where your specification skills need development. Review AI code systematically. Develop a review checklist for your domain. What are the common failure modes? What edge cases does AI typically miss? What security or performance issues should you always check? Make your review process explicit rather than intuitive. Notice your resistance. When you feel the urge to implement instead of delegate, pause. Ask yourself: is this the best use of my time, or am I attached to the implementation itself? Sometimes manual implementation is the right choice. Often it's not. Awareness of the pattern is the first step toward changing it. Embrace the discomfort. Becoming a better delegator means becoming comfortable with not being the one who writes the code. This is uncomfortable at first. Sit with that discomfort. It's the feeling of growth. Final Thoughts The shift from implementation speed to specification clarity isn't a threat to engineering. It's an evolution of engineering. The core skills remain: understanding problems, designing solutions, building systems that work. What changes is where human effort creates the most value. Your coding speed used to be the bottleneck. Now it's your clarity. Your ability to specify what "done" looks like before you start. Your willingness to delegate implementation so you can focus on the thinking that only you can do. The engineers who thrive with AI aren't the ones who prompt better. They're the ones who think clearer. They're the ones who let go faster. Your passion for code made you great. Now the question is whether you can channel that passion into clarity, into specification, into the harder and more valuable work of deciding what should be built rather than building it yourself. The answer will shape your career for the next decade. If this resonated with you, I'd love to hear your thoughts. What's your experience with AI delegation? Where do you struggle with specification clarity? You can find me on LinkedIn or reach out through the contact form on this site. And if you want more perspectives on building software in the age of AI, consider following along. I write regularly about the intersection of engineering craft and practical AI adoption. --- # Evaluate: Why Human Judgment Is Non-Negotiable URL: https://www.ivanturkovic.com/2026/02/03/evaluate-human-judgment-non-negotiable/ Published: 2026-02-03 Topics: AI, AI-Driven Development We have arrived at the phase of ADD where the most important human skill comes into play. You have written a specification. You have generated code using appropriate context and patterns. Now you must determine whether that code is actually correct. This is not a formality. AI-generated code can be syntactically correct, pass basic tests, and still be fundamentally wrong. It can implement something plausible that is not what you specified. It can handle the happy path while silently failing on edge cases. It can introduce subtle vulnerabilities that no linter or automated tool will catch. Evaluation is the quality gate that separates disciplined AI usage from hope-based development. And it requires skills that only humans bring. Why Evaluation Cannot Be Skipped or Automated Away The temptation to skip evaluation is real. You wrote a detailed specification. You used good prompt patterns. The generated code looks clean and well-structured. Why not just run the tests and move on? Because the tests themselves might be incomplete. Because "looks clean" is not the same as "is correct." Because the AI's failure modes are subtle, not obvious. There is also a temptation to automate evaluation entirely: let the linter check style, let the tests check correctness, let static analysis check security. These tools are valuable. They catch real issues. But they check for known patterns of failure, not for the kinds of novel errors that AI can introduce. An AI might generate a function that perfectly implements a caching layer but invalidates cached entries using the wrong key format. The tests pass because the test data happens to work with both key formats. The linter finds nothing wrong. Static analysis sees correct caching patterns. Only a human reading the code and understanding the system recognizes that this caching approach will fail in production when key formats diverge. I have encountered this exact category of failure multiple times. The code works. The tests pass. Everything looks correct. But a detail that requires understanding the full system context is wrong, and no automated tool has the system-level awareness to catch it. Automated tools complement human evaluation. They do not replace it. Use them to catch the easy issues so your human evaluation time can focus on the hard ones. The Multiple Dimensions of Evaluation Effective evaluation examines code across five dimensions. Each catches different categories of failure, and none is sufficient alone. Correctness: Does It Implement the Specification? The most fundamental question. Does the code do what the specification says it should do? This sounds simple, but it requires careful comparison between the specification and the implementation. Check each requirement. Does the function accept the specified input types? Does it return the specified output types? Does it handle each specified error condition? Does it follow each specified business rule? Common AI failures in correctness: Partial implementation. The AI implements most of the specification but quietly omits a requirement. It generates eight out of ten validation rules. The two missing rules are not immediately obvious because the code looks complete. Plausible alternatives. The AI implements something similar to the specification but not identical. You specified a function that returns the first matching item. The AI returns the last matching item. Both implementations are reasonable; only one matches the specification. Specification interpretation. The AI interprets ambiguous parts of the specification differently than you intended. This reveals specification gaps, which is useful, but only if you catch the divergence during evaluation. Check correctness by tracing through the specification point by point. Do not rely on an overall impression of "this looks right." Compare systematically. Fitness: Does It Fit the Existing System? Correct code that does not fit your system is wrong in context. Fitness evaluation examines whether the generated code integrates properly with the surrounding codebase. Pattern consistency. Does the code follow the same patterns as the rest of your system? If your codebase uses repository pattern for data access, does the generated code use repository pattern, or did the AI invent its own approach? Naming conventions. Do variable names, function names, and class names follow your team's conventions? This sounds trivial, but inconsistent naming creates real confusion for future developers. Error handling alignment. Does the generated code handle errors the same way the rest of your system does? If your system uses typed errors with error codes, does the generated code produce typed errors with error codes, or does it throw generic exceptions? Abstraction levels. Does the generated code operate at the same level of abstraction as surrounding code? AI sometimes produces code that is more granular or more abstract than the context requires, creating a mismatch that makes the codebase harder to understand. Fitness evaluation requires knowledge of your codebase. This is one reason why the skills discussed in Post 1 of this series remain essential. You cannot evaluate fitness for a system you do not understand. Security: Does It Introduce Vulnerabilities? Security evaluation is particularly important for AI-generated code because AI models replicate patterns from training data, including insecure patterns. The code may look correct while containing vulnerabilities that are not visible to casual review. Input handling. Does the code validate and sanitize all external input? Does it use parameterized queries for database access? Does it encode output for the appropriate context (HTML, URL, SQL)? Authentication and authorization. If the code involves access control, does it check permissions correctly? Does it fail closed (denying access by default) rather than failing open? Data exposure. Does the code log sensitive data? Does it include sensitive information in error messages? Does it expose internal details through API responses? Cryptographic practices. If the code involves encryption, hashing, or token generation, does it use current recommended approaches? AI models sometimes generate code using deprecated cryptographic libraries or approaches that were common in older training data. Timing vulnerabilities. Does the code compare secrets using constant-time comparison? Does it reveal information through response timing differences? Security evaluation requires specific knowledge. If your team lacks security expertise, consider whether AI-generated code touching security-sensitive areas should receive external review. Performance: Will It Scale? AI-generated code often works correctly at small scale but contains performance issues that emerge under production load. Algorithmic complexity. What is the time complexity of the generated code? Is it appropriate for expected input sizes? AI sometimes generates O(n²) solutions when O(n) or O(n log n) approaches are needed. Memory usage. Does the code load unnecessary data into memory? Does it create unnecessary copies? For large data sets, does it use streaming or pagination? Database efficiency. Does the code produce efficient queries? Does it avoid N+1 query patterns? Does it use appropriate indexing? Concurrency. If the code handles concurrent operations, does it use appropriate synchronization? Does it avoid unnecessary blocking? Performance evaluation requires understanding of your system's scale. Code that performs adequately for 100 records might be catastrophically slow for 100,000. Evaluate against realistic production volumes, not test data sizes. Maintainability: Can Others Understand It? The final dimension addresses the long-term health of your codebase. Code is read far more often than it is written. AI-generated code must be understandable by humans who encounter it in the future. Clarity of intent. Does the code clearly communicate what it does and why? Are complex sections explained? Would a new team member understand this code without the specification? Appropriate complexity. Is the code as simple as it could be? AI sometimes generates unnecessarily complex solutions: using advanced features where simple ones would suffice, or abstracting things that do not need abstraction. Testability. Is the code structured so it can be easily tested? Does it have clear inputs, outputs, and side effects? Or does it entangle concerns in ways that make testing difficult? Documentation accuracy. If the AI generated comments or documentation, are they accurate? AI-generated comments sometimes describe what the code should do rather than what it actually does, especially after iterations that changed the implementation without updating comments. Common AI Failure Modes Understanding how AI commonly fails helps you know where to focus evaluation attention. Plausible but incorrect logic. The code follows a reasonable approach but gets a detail wrong. An off-by-one error in a boundary check. A comparison that should be inclusive but is exclusive. A sort that is ascending when it should be descending. These errors are hard to spot because the code reads naturally. Training data bias. The AI generates code reflecting common patterns in its training data, which may not be current best practices. Using older library versions, deprecated API calls, or patterns that have known issues. The code works but uses approaches that experienced developers would avoid. Hidden assumptions. The AI makes assumptions about data format, size, encoding, or availability that are not visible in the code. These assumptions hold for the test data but fail for production data. Confident incorrectness. The AI generates code with authoritative-looking comments that describe incorrect behavior. "This function returns the median value" when it actually returns the mean. The comment creates false confidence during review. Copy-paste drift. When the AI generates multiple similar functions, later functions may drift from the pattern of earlier ones. The first three are correct; the fourth has a subtle variation because the AI's attention shifted. Evaluation Techniques and Heuristics Read the code without the specification first. Before checking against your specification, read the code and form your own understanding of what it does. Then compare that understanding to the specification. This catches cases where the code does something different from what was specified but looks reasonable enough that specification-focused review might miss it. Trace through edge cases manually. Pick the most challenging edge cases from your specification and trace the code's execution path for each. Do not trust your intuition that "it probably handles this correctly." Walk through the code line by line with concrete edge case values. Check what is not there. AI-generated code often fails by omission. Look for what should be present but is not: missing null checks, missing error handling, missing validation, missing logging. The code that is present might be correct; the danger is in what was left out. Compare to your exemplar. If you used the Exemplar Pattern during generation, compare the output to the exemplar. Does it follow the same patterns? Where it differs, is the difference intentional or accidental? Ask "what could go wrong?" For each significant piece of logic, imagine failure scenarios. What if this API call times out? What if this data is null? What if this string is in an unexpected encoding? If the code does not handle these scenarios, add them to your evaluation notes. Look for the second-order effects. First-order evaluation asks "does this code work?" Second-order evaluation asks "what does this code change about the system?" Does it introduce new dependencies? Does it change the performance profile of an existing workflow? Does it alter error behavior in ways that affect calling code? AI-generated code exists in a system, and its effects extend beyond its immediate function. Time-box your evaluation. For routine tasks, set a time limit. If you cannot evaluate the code thoroughly in that time, the code may be too complex for the task. Consider decomposing the task or simplifying the specification. Evaluation should be thorough but proportionate to the risk and complexity of the generated code. The Skill Requirement Here is the uncomfortable truth about evaluation: you cannot evaluate what you do not understand. If you do not know how SQL injection works, you cannot evaluate whether generated code is vulnerable to it. If you do not understand time complexity, you cannot evaluate whether an algorithm will scale. If you do not know your codebase's architecture, you cannot evaluate whether generated code fits it. This is why the skill maintenance practices I described in Post 1 of this series are not optional extras. They are prerequisites for effective evaluation. The better you understand code, architecture, and engineering principles, the more effective your evaluations become. This also means that evaluation speed improves with practice. The first time you systematically evaluate AI-generated code across all five dimensions, it takes significant time. As you build pattern recognition for common AI failure modes, you develop intuitions about where to focus attention. Experienced evaluators check the same dimensions but do so faster because they recognize patterns. Do not let the time cost of thorough evaluation discourage you. The alternative is not saving time. The alternative is spending time later debugging production issues, fixing security vulnerabilities, or untangling architectural drift. Evaluation time is an investment with measurable returns. Teams can also build evaluation efficiency by sharing their findings. When one developer discovers a new AI failure mode, sharing that discovery means the entire team can check for it. Over time, the team develops a collective awareness of where AI-generated code tends to fail in their specific context. This collective knowledge makes individual evaluations faster and more effective. When Evaluation Fails Repeatedly Sometimes generated code fails evaluation consistently. Each attempt produces output that does not meet the specification, does not fit the system, or introduces problems across multiple dimensions. This is a signal, and you should read it correctly. If the code fails on correctness repeatedly, the specification may be ambiguous or the task may exceed the AI's reliable capability. Consider revising the specification or decomposing the task further. If the code fails on fitness repeatedly, you may need to provide more context. Include more exemplars. Add more constraints. Make the system's patterns explicit in the specification. If the code fails on security or performance repeatedly, the task may be in a category where AI assistance is unreliable. Consider writing security-critical or performance-critical code manually. If multiple dimensions fail simultaneously, the task may not be suitable for AI-assisted generation at all. Recognizing when to step away from AI and write code directly is itself a valuable skill. Repeated evaluation failure should prompt reflection, not frustration. Each failure teaches you something about what the AI can and cannot do reliably, and that knowledge improves your judgment for future tasks. From Evaluation to Integration Code that passes evaluation across all five dimensions moves to the Integrate phase. But evaluation does not end with a binary pass/fail. Good evaluation produces notes: minor concerns, potential improvements, areas to monitor. These notes travel with the code into integration. In Post 11, I will provide concrete evaluation checklists that operationalize the dimensions covered here. These checklists transform the principles of evaluation into repeatable practices that ensure consistency across team members and across tasks. For now, begin practicing multi-dimensional evaluation. The next time you review AI-generated code, consciously check each dimension: correctness, fitness, security, performance, maintainability. Notice which dimensions you naturally focus on and which you tend to skip. The dimensions you skip are where problems hide. Let's Continue the Conversation Which evaluation dimension do you find most challenging? Where have you discovered AI failures that surprised you? What evaluation techniques have you developed beyond what is covered here? Share your experience in the comments. Evaluation is a skill that sharpens with practice and with exposure to others' approaches. --- # Prompt Patterns Catalog, Part 2: Iteration, Verification, and Persona URL: https://www.ivanturkovic.com/2026/02/02/prompt-patterns-iteration-verification-persona/ Published: 2026-02-02 Topics: AI, AI-Driven Development, Software Engineering In the previous post, I introduced three foundational prompt patterns: Decomposition for breaking complex tasks into manageable units, Exemplar for teaching by example, and Constraint for defining boundaries. These patterns address the most common generation challenges. This post completes the catalog with three more patterns, then addresses the practical question of building and maintaining a pattern library for your team. The Iterative Refinement Pattern When to use: The task is complex enough that first-attempt output is unlikely to be complete or correct, but the general direction is likely to be useful. You want to build toward a solution through structured conversation rather than hoping for perfection in one shot. Core idea: Treat the first generation as a draft. Use structured follow-up prompts to refine specific aspects while preserving what works. Each iteration addresses a defined concern rather than asking the AI to vaguely "improve" the output. How it works: Iterative Refinement is not the same as the ad-hoc iteration that characterizes unstructured AI usage. In unstructured usage, you see something wrong, mention it, and hope the AI fixes it. In the Iterative Refinement Pattern, you plan the iteration sequence before you start. The structure follows a predictable sequence. First generation: Core logic. Provide the full specification and ask for the core implementation. Do not expect perfection. Expect a working foundation. Second generation: Error handling. Review the core logic. If the foundation is sound, ask the AI to add comprehensive error handling. Provide specific error scenarios and expected responses. "Now add error handling for the following scenarios: [list]. Use our ErrorResult type as shown in the specification." Third generation: Edge cases. With core logic and error handling in place, address edge cases. "The current implementation does not handle [specific edge case]. Add handling for: [list with expected behavior for each]." Fourth generation: Optimization. If performance matters, address it after correctness is established. "The current implementation loads all records into memory. Refactor to use streaming with a batch size of 100, preserving the existing interface and error handling." Each iteration specifies what to change and what to preserve. This prevents the common problem where fixing one thing breaks another. "Preserve the existing validation logic and error handling. Only modify the data retrieval approach." Key discipline: specify what to preserve. Without explicit preservation instructions, the AI may rewrite working code alongside the changes you requested. Always state what should not change alongside what should. Example: First prompt: "Generate the OrderProcessor class per the specification. Focus on the core processing pipeline: validate order, check inventory, calculate pricing, and create the order record." Review output. Core pipeline is correct. Error handling is minimal. Second prompt: "The core pipeline looks correct. Now add error handling without modifying the existing pipeline logic. Handle these specific scenarios: InsufficientInventoryError when requested quantity exceeds available PricingCalculationError when product pricing data is missing OrderCreationError when database insertion fails Each error should include the order_id and a descriptive message. Log errors using our structured logging pattern shown in the specification." Review output. Error handling is correct. Edge case with zero-quantity line items is not handled. Third prompt: "Error handling looks good. Preserve all existing logic. Add handling for these edge cases: Order with zero line items: raise EmptyOrderError before processing Line item with zero quantity: skip the line item and log a warning Duplicate product IDs in line items: merge quantities before processing" Each iteration is focused, specific, and builds on verified work. When not to use: When the task is simple enough for single-shot generation. When the first output is fundamentally wrong (not just incomplete). If the foundation is flawed, iterating on it compounds the problem. Regenerate from an improved specification instead. The Verification Pattern When to use: You want the AI to review its own output before you invest time in detailed evaluation. You want to catch surface-level issues like missing error handling, unused imports, or inconsistencies with the specification. Core idea: After generation, ask the AI to check its output against your specification. Provide the specification again and ask specific verification questions. How it works: After the AI generates code, prompt it to verify specific aspects: "Review the code you generated against the specification. Specifically check: Does the function handle all error cases listed in the specification? Are all input validation rules implemented? Does the response format match the specified schema? Are there any unused imports or variables?" The AI reviews its own output and often catches obvious issues: a missing error case, an incorrect return type, a validation rule that was specified but not implemented. Important limitations: The Verification Pattern has real value, but it also has real limitations that you must understand to use it effectively. The AI has the same blind spots when verifying as when generating. If the AI's training data leads it toward a particular approach, verification using the same model will often confirm that approach. The biases that produce errors can also prevent detecting them. Verification is better for surface-level issues than deep logic errors. The AI can reliably check whether all specified error cases are handled. It is less reliable at identifying subtle logic errors, race conditions, or security vulnerabilities. These require human evaluation. Verification can produce false confidence. When the AI reports "all checks pass," you might relax your own evaluation. This is dangerous. Treat AI verification as a preliminary filter, not a substitute for human evaluation. It catches some issues before you look, saving you time, but it does not eliminate the need for you to look. Do not ask "is this code correct?" This question is too vague and invites a confident "yes." Ask specific questions that have concrete answers. "Does the function return a Decimal type as specified?" is verifiable. "Is this code correct?" is not. Example: After generating a data validation function: "Before I review the code, please verify the following against the original specification: List each validation rule from the specification and confirm whether it is implemented in the code. For each error condition in the specification, show me the line that handles it. Check whether any specified edge case (empty string, null input, string exceeding 255 characters) is not handled. Verify that error codes match the specification table exactly." The AI responds with a point-by-point review. It might discover that the 255-character limit check was implemented as 256, or that one error code does not match the specification. These are exactly the kind of issues that are easy to miss in manual review but straightforward for the AI to catch. You still need to evaluate the code yourself. But the AI has already caught the easy issues, and your evaluation can focus on deeper concerns. When not to use: When you need assurance about complex logic, security, or performance. These require human judgment. Also avoid relying on verification when the specification itself might be incomplete, since the AI will verify against what you specified, not against what you actually need. The Persona Pattern When to use: The generation task benefits from a specific perspective or expertise. You want the AI to prioritize certain concerns that a particular type of expert would prioritize. Core idea: Establish an expertise context that shapes how the AI approaches the task. "You are a senior security engineer" focuses attention on security considerations. "You are a performance engineer" focuses attention on efficiency and scalability. How it works: Add a persona instruction to your prompt that establishes the perspective you want the AI to adopt: "Approach this as a senior security engineer would. Prioritize input validation, output encoding, and defense in depth. Flag any patterns that could lead to injection, exposure of sensitive data, or privilege escalation." Or: "Approach this as a database performance specialist. Prioritize query efficiency, appropriate indexing, and minimizing round trips. Flag any patterns that could cause N+1 queries or full table scans." When Persona helps: Personas genuinely change output when they activate domain-specific knowledge the AI has but would not otherwise prioritize. Security-focused personas tend to produce more defensive code. Performance-focused personas tend to produce more efficient code. Accessibility-focused personas tend to catch ARIA issues that generic generation misses. The effect is real but modest. Think of it as adjusting the AI's attention, making it more likely to consider certain factors rather than fundamentally changing its capabilities. When Persona is theater: Personas do not give the AI capabilities it does not have. "You are the world's best quantum computing expert" does not make the AI better at quantum computing. It just makes it more confident about quantum computing topics, which can actually be worse because increased confidence without increased capability produces more authoritative-sounding errors. Personas also do not overcome fundamental model limitations. If the AI cannot reliably handle a type of task, adding a persona will not fix this. It may make the AI try harder, but trying harder with the same underlying capabilities produces marginal improvement at best. A useful heuristic: if the persona activates knowledge the AI already has, it helps. If the persona claims expertise the AI does not have, it is theater. Example: Suppose you need to generate a user authentication endpoint. Without persona: "Generate an authentication endpoint per the specification." The AI produces a functional endpoint. It may or may not include rate limiting, brute-force protection, or timing-safe comparison. With persona: "Approach this as a senior security engineer reviewing authentication code for a financial services company. Generate an authentication endpoint per the specification. In addition to the functional requirements, ensure the implementation addresses: brute-force protection, timing-safe password comparison, secure session handling, and appropriate audit logging." The persona focuses the AI's attention on security concerns that the specification might not have enumerated in detail. The output is more likely to include security best practices that a security-focused developer would consider standard. Notice that the persona works here because the AI has substantial training data about authentication security. The persona activates relevant knowledge rather than claiming knowledge that does not exist. When not to use: When the task does not benefit from a particular perspective. When you are tempted to use persona as a substitute for a clear specification. "You are an expert at understanding what I want" is not a useful persona. Specification should provide clarity, not persona. Building Your Team's Pattern Library Six patterns are a starting point. Your team will develop additional patterns through practice. The challenge is capturing and sharing what works. Document patterns when they produce consistently good results. If a particular approach to generation works reliably across multiple tasks, it is a candidate for your pattern library. Document it with a name, a description of when to use it, how to structure it, and an example. Use a consistent format. Each pattern entry should include: the pattern name, when to use it (the problem it solves), how to structure it (the approach), a concrete example, and when not to use it (limitations). Share patterns through code review. When reviewing AI-generated code, discuss which patterns were used and how well they worked. This normalizes pattern usage and spreads knowledge about what is effective. Include negative examples. When a pattern fails, document that too. "We tried using the Persona Pattern for database optimization, but the AI produced recommendations that were not compatible with our PostgreSQL version." Negative examples prevent teammates from repeating failed approaches. Retiring Patterns That Stop Working AI models change. Patterns that worked with one model version may not work with another. The effectiveness of specific prompting approaches can shift as models are updated. Watch for patterns that used to produce good results but have degraded. If a pattern starts producing inconsistent output, test whether it is the pattern or the task. If the pattern works for other tasks but not for new ones, the new tasks might need different patterns. If the pattern fails broadly, it may be time to retire or revise it. Do not hold onto patterns out of habit. The goal is effectiveness, not tradition. Your pattern library should be a living collection that adapts to the tools you use. The Generate Phase Is Complete With the specification templates from Post 6, the generation disciplines from Post 7, and the prompt patterns from Posts 8 and 9, you have a comprehensive toolkit for the Generate phase of ADD. But generation is only the middle of the cycle. The code it produces is a candidate, not a conclusion. In the next post, I will begin covering the Evaluate phase: why human judgment is non-negotiable, what dimensions of evaluation matter, and how to build the skills and habits that catch what AI confidence obscures. Let's Continue the Conversation Which of the six patterns across these two posts do you use most frequently? Have you found any that do not work as expected? What patterns has your team developed that are not covered here? Share your experience in the comments. The best pattern libraries come from collective practice, and your discoveries can help others generate more effectively. --- # Prompt Patterns Catalog: Decomposition, Exemplar, Constraint URL: https://www.ivanturkovic.com/2026/02/01/prompt-patterns-decomposition-exemplar-constraint/ Published: 2026-02-01 Topics: AI, AI-Driven Development Software developers are familiar with design patterns. The Gang of Four cataloged reusable solutions to recurring problems in object-oriented design. You learn patterns like Strategy, Observer, and Factory not because they are theoretically interesting but because they solve problems you encounter repeatedly. Once you know the pattern, you recognize the problem and reach for a proven solution. Prompt patterns serve the same purpose for AI collaboration. They are reusable approaches to recurring challenges in the Generate phase. Like design patterns, they have names, contexts where they apply, and structures you can follow. Like design patterns, they become more valuable as you internalize them and recognize when to apply them. This post covers three foundational patterns. The next post will cover three more, plus guidance on building your team's pattern library. Why Patterns Matter Without patterns, every generation is an improvisation. You face a task, think about how to prompt the AI, and construct an approach from scratch. Sometimes this works well. Sometimes it does not. The inconsistency means you cannot predict outcomes or improve systematically. Patterns provide structure for this process. When you recognize that a task is too complex for single-shot generation, you reach for the Decomposition Pattern. When you need the AI to match your codebase's style, you reach for the Exemplar Pattern. When the AI keeps introducing unwanted elements, you reach for the Constraint Pattern. Over time, patterns become habitual. You stop thinking about how to structure your prompts and start thinking about which pattern fits the situation. This frees cognitive resources for the actual problem: what should this code do? Patterns also create shared vocabulary within teams. When a colleague says "I used the Exemplar Pattern with our auth module," everyone understands what that means. This shared vocabulary accelerates code review, knowledge sharing, and onboarding. The Decomposition Pattern When to use: The task is too complex or too large for reliable single-shot generation. You observe that the AI produces partial solutions, loses track of requirements, or generates inconsistent code when given the full specification at once. Core idea: Break the complex task into smaller, self-contained units. Generate each unit separately with focused context. Integrate the units after each passes evaluation. How it works: Decomposition is not just splitting a task into arbitrary pieces. Effective decomposition follows the natural boundaries of the code: layers, concerns, or dependency order. Decompose by layer. If you need a feature that spans data access, business logic, and API endpoints, generate each layer separately. Start with the data layer because other layers depend on it. Then generate business logic that uses the data layer. Then generate the API endpoint that uses the business logic. Each generation has focused context and a clear specification. Decompose by concern. If you need a complex function that validates input, transforms data, and handles errors, consider generating each concern separately. Generate the validation logic with its own specification. Generate the transformation logic with its own specification. Then generate the orchestrating function that combines them. Decompose by dependency order. Identify which pieces other pieces depend on. Generate the foundational pieces first, validate them, then provide them as context for dependent pieces. This ensures each generation builds on verified code rather than assumptions. Example: Suppose you need to build a user registration system that validates input, checks for duplicate accounts, creates the account, sends a verification email, and returns appropriate responses. Instead of one massive specification, decompose: First generation: Input validation function. Specification focuses on validation rules, error messages, and the ValidationResult type. Second generation: Duplicate check function. Specification focuses on the database query, what constitutes a duplicate, and the response type. Provide the ValidationResult type from the first generation as context. Third generation: Account creation function. Specification focuses on creating the database record, generating the verification token, and handling creation failures. Provide the validated input types and duplicate check interface as context. Fourth generation: Email sending function. Specification focuses on composing and sending the verification email. Provide the account type and verification token type as context. Fifth generation: Registration orchestrator. Specification focuses on calling each function in order, handling errors at each step, and returning the appropriate response. Provide the interfaces of all four functions as context. Each generation is tractable. Each produces code you can evaluate independently. The final integration is straightforward because each component has a clear interface. When not to use: When the task is simple enough for reliable single-shot generation. Decomposition adds overhead. If the AI consistently produces good results for a task in a single generation, decomposition is unnecessary complexity. The Exemplar Pattern When to use: You need generated code to match specific patterns, conventions, or styles from your existing codebase. Verbal descriptions of your conventions are insufficient or impractical. The AI keeps producing code that is functionally correct but stylistically inconsistent with your system. Core idea: Instead of describing what you want, show what you want. Include concrete examples from your codebase as part of the specification. The AI learns your patterns from the examples and replicates them in the generated code. How it works: Select one or two examples from your codebase that embody the patterns you want the generated code to follow. Include these examples in your specification with annotations explaining what aspects of the examples should be emulated. The key is choosing the right examples. Good exemplars: Are structurally similar to the target code. If you are generating an API endpoint, provide an example of an existing API endpoint. If you are generating a data transformation, provide an existing data transformation. Are clean and representative. Choose examples that follow your conventions correctly, not examples that are historical exceptions or known technical debt. The AI will emulate what you show, including flaws. Are annotated with what matters. Do not just paste code. Explain what aspects the AI should emulate. "Note the error handling pattern in lines 15 through 22" or "Follow the same response structure" or "Use the same logging approach." Without annotation, the AI may emulate the wrong aspects. Example: Suppose you need a new API endpoint for updating user preferences. Your codebase has a consistent pattern for API endpoints. Your specification includes the functional requirements and constraints, plus: "Follow the patterns established in the existing endpoint below. Specifically, emulate: The input validation approach using our validate() decorator The error handling pattern with specific error types The response structure with data and metadata fields The logging pattern with structured context" @router.put("/users/{user_id}/profile") @validate(UpdateProfileSchema) async def update_profile(user_id: UUID, data: UpdateProfileRequest) -> APIResponse: logger.info("profile_update_started", user_id=str(user_id)) try: user = await user_service.get_user(user_id) if not user: raise NotFoundError(f"User {user_id} not found") updated = await user_service.update_profile(user, data) logger.info("profile_update_completed", user_id=str(user_id)) return APIResponse( data=UserProfileResponse.from_model(updated), metadata={"updated_at": updated.updated_at.isoformat()} ) except NotFoundError: raise except ValidationError as e: raise BadRequestError(str(e)) except Exception as e: logger.error("profile_update_failed", user_id=str(user_id), error=str(e)) raise InternalError("Failed to update profile") The AI now has a concrete model to follow. The generated endpoint for updating preferences will naturally adopt the same validation decorator, error handling pattern, response structure, and logging approach. This produces code that fits your system far better than verbal instructions like "use consistent error handling." When not to use: When you are deliberately establishing new patterns that differ from existing ones. When your existing examples are poor quality and should not be replicated. When the task is so different from anything in your codebase that examples would be misleading rather than helpful. The Constraint Pattern When to use: The AI keeps introducing elements you do not want. It reaches for external libraries when you need pure standard library code. It uses patterns that do not fit your architecture. It generates code that works but violates your team's conventions or your system's requirements. Core idea: Define what the code must not do, not just what it should do. Explicit constraints prevent the AI from making unwanted choices that look reasonable in isolation but are wrong for your context. How it works: Add a constraints section to your specification that lists prohibited approaches, patterns, libraries, or behaviors. Be specific. Vague constraints like "keep it simple" are open to interpretation. Precise constraints like "do not use any external dependencies" leave no room for misinterpretation. Categories of useful constraints: Dependency constraints. "Use only the Python standard library. No third-party packages." "Use our existing database utility module, do not import SQLAlchemy directly." "Do not add new npm dependencies." Pattern constraints. "Do not use inheritance for this; use composition." "Do not use global state." "Do not use callback-based async; use async/await throughout." "Do not use the singleton pattern." Performance constraints. "Do not load the entire dataset into memory; use streaming." "Do not make sequential API calls; use concurrent requests." "Do not use recursive approaches for inputs exceeding 1,000 elements." Security constraints. "Do not log any field from the UserCredentials object." "Do not include secrets in error messages." "Do not use string concatenation for SQL queries; use parameterized queries exclusively." Compatibility constraints. "Do not use features introduced after Python 3.9." "Do not use ES modules; this project uses CommonJS." "Do not use any API marked as deprecated in our codebase." Style constraints. "Do not use abbreviations in variable names." "Do not use default exports." "Do not use ternary expressions for complex conditions." Example: Suppose you need a function to parse configuration files. Your specification includes the functional requirements, plus: "Constraints: Do not use any external libraries. Use only Python standard library modules (json, configparser, os, pathlib). Do not use eval() or exec() for any purpose. Do not read from environment variables directly; accept an optional env_overrides dict parameter instead. Do not raise generic Exception; use ConfigurationError for all error conditions. Do not use mutable default arguments. Do not cache results at module level; return fresh results each time." Without these constraints, the AI might reasonably reach for PyYAML or toml libraries, use eval for dynamic config processing, read environment variables directly, raise generic exceptions, or cache for performance. Each of these choices could be defensible in isolation but wrong for your specific context. Combining constraints with exemplars. The Constraint Pattern is often most powerful when combined with the Exemplar Pattern. Show an example of code that follows your conventions and explicitly state what must not change. "Follow the pattern in the example above. Do not modify the error handling approach or the logging structure." When not to use: When you have so many constraints that they effectively dictate the implementation. If your constraints specify every detail, you have written the code in constraint form and the AI is just translating. At that point, consider whether the AI is adding value or whether writing the code directly would be faster. Combining Patterns These patterns are not mutually exclusive. Complex tasks often benefit from multiple patterns applied together. A common combination: use the Decomposition Pattern to break a complex task into units, then use the Exemplar Pattern on each unit to ensure consistency, and add the Constraint Pattern to prevent known problematic approaches. Another combination: use the Constraint Pattern to define system-wide rules in your system prompt, then use the Exemplar Pattern in individual specifications. The constraints prevent global problems while the exemplars guide local implementation. The goal is not to use as many patterns as possible but to use the patterns that address the specific challenges of each generation task. With practice, pattern selection becomes intuitive. You recognize the type of challenge and reach for the appropriate pattern without deliberate analysis. Building Pattern Awareness Start by noticing when generation produces unwanted results. Each failure is an opportunity to identify which pattern would have prevented it. If the AI produced code that was too complex or lost coherence, the Decomposition Pattern might have helped. If the AI produced functionally correct code that did not match your conventions, the Exemplar Pattern might have helped. If the AI made choices you did not want, the Constraint Pattern might have helped. In the next post, I will cover three more patterns: Iterative Refinement, Verification, and Persona. I will also discuss how to build and maintain a team pattern library that evolves with your experience. Let's Continue the Conversation Which of these patterns addresses your most common generation frustration? Have you developed your own prompt patterns that reliably produce good results? What problems do they solve? Share your patterns in the comments. The best pattern libraries are built from collective experience. --- # Generate: The Art of Effective AI Collaboration URL: https://www.ivanturkovic.com/2026/01/31/generate-effective-ai-collaboration/ Published: 2026-01-31 Topics: AI, AI-Driven Development, Software Engineering Generation is where the visible work happens. You provide input, and the AI produces code. This is the moment most developers think of when they imagine AI-assisted development. It is also where most developers start, jumping directly to generation without the specification work that should precede it. In the ADD cycle, generation is the second phase, not the first. This ordering is deliberate. Generation without specification produces code built on hidden assumptions. Generation with specification produces code you can evaluate against explicit criteria. But even with a good specification, generation requires its own disciplines. Context management, iteration strategy, and resistance to premature acceptance all determine whether generation produces useful output or creates more work than it saves. Generation in Context The Generate phase sits between Specify and Evaluate. This position shapes how generation should work. From specification, generation receives clear requirements, constraints, and context. The specification defines what success looks like. It provides the criteria against which output will be judged. Without this input, generation is guessing. With it, generation has direction. To evaluation, generation provides candidate code. This code is not finished until it passes evaluation. The discipline of the Generate phase is to produce output worth evaluating, not to produce final code. Treating generated code as a draft rather than a finished product changes how you interact with the AI and how you respond to its output. This positioning means generation is not about getting the AI to produce perfect code on the first try. It is about producing code that, combined with human evaluation and refinement, efficiently reaches a correct solution. Sometimes the first generation is exactly right. Often it requires iteration. The measure of effective generation is total time to correct code, not impressiveness of initial output. Context Management: What to Include The AI generates code based on the context you provide. Better context produces better output. But context management is not simply about providing more information. It requires judgment about what helps and what creates noise. Always include the specification. This seems obvious, but developers often paraphrase or summarize their specification rather than providing it directly. Give the AI the complete specification you wrote. The precision you invested in writing it should be preserved in providing it. Include relevant code examples. If your specification references patterns from your codebase, include those patterns. If the new code must implement an interface, include that interface definition. If there are similar functions the AI should emulate, include them. Concrete examples communicate more than abstract descriptions. Include interface definitions. When the generated code must interact with existing code, provide the signatures, types, and contracts it must respect. The AI cannot match interfaces it cannot see. This includes not just function signatures but data types, error types, and any protocols the code must follow. Include error handling patterns. If your codebase has a consistent approach to error handling, show it. Include an example of how errors are created, propagated, and logged. Without this context, the AI will invent its own error handling, which may not match your conventions. Include relevant configuration. If the code depends on configuration values, environment variables, or feature flags, describe them. The AI needs to know what can be configured and what is fixed. Context Management: What to Exclude More context is not always better. Excessive or irrelevant context creates noise that can degrade output quality and consume context window space that could be used for more relevant information. Exclude irrelevant code. If you are generating a validation function, the AI does not need to see your entire data access layer. Include only code that the new code will interact with or should emulate. Resist the temptation to provide "everything just in case." Exclude excessive conversation history. In multi-turn conversations, earlier exchanges may no longer be relevant. If you have iterated through several approaches and settled on a direction, summarize that direction rather than including the full history of dead ends. The AI will give weight to information in its context, including information about abandoned approaches. Exclude redundant information. If your specification already describes the error handling approach, you do not need to repeat it in additional context. Redundancy creates opportunities for inconsistency if the repetitions differ slightly. Exclude speculative requirements. Include what the code must do now, not what it might need to do later. Future requirements create ambiguity about current scope. If extensibility is a requirement, state it as a constraint, but do not include detailed specifications for features you are not building yet. The goal is a context that is complete without being cluttered. Every piece of context should earn its place by contributing to better generation. The Role of System Prompts System prompts establish baseline context that applies to all generations in a session or project. They are particularly valuable for context that would otherwise need to be repeated in every specification. Effective system prompts include: Technology stack and versions. "All code should target Python 3.11 and use type hints throughout." Coding conventions. "Follow PEP 8 for formatting. Use snake_case for functions and variables, PascalCase for classes." Architectural patterns. "This codebase uses a hexagonal architecture. Business logic should be independent of infrastructure concerns." Security requirements. "All user input must be validated. Never log sensitive data including passwords, tokens, or personal identifiers." Error handling philosophy. "Prefer explicit error types over generic exceptions. All functions that can fail should document their failure modes." System prompts should be stable and broadly applicable. Requirements specific to a particular task belong in the specification, not the system prompt. The system prompt establishes the environment; the specification defines the task. Teams should maintain their system prompt as a shared artifact. When conventions change, the system prompt should be updated. When new team members join, the system prompt teaches them the baseline expectations. Iterative Generation: Using Output to Refine The first generation is often not the final answer. Effective use of AI involves iteration, but iteration should be purposeful rather than random. Use generation failures to improve specification. When the AI produces unexpected output, ask why. Often the answer is that your specification was ambiguous in ways you did not recognize. The AI's interpretation, even if wrong, reveals where your specification could be clearer. Update the specification and regenerate. Distinguish specification gaps from generation errors. If the AI produced exactly what you asked for but not what you wanted, that is a specification problem. If the AI misinterpreted a clear specification, that is a generation problem. The response differs: specification problems require specification revision; generation problems may require prompt adjustment or regeneration. Build incrementally for complex tasks. If a task is too complex for reliable single-shot generation, decompose it. Generate the core logic first, validate it, then generate the supporting code. Each generation builds on verified output rather than accumulating errors. Know when to regenerate versus refine manually. Sometimes the generated code is close enough that manual editing is faster than another generation round. Sometimes the generated code is so far off that starting fresh is better. Develop judgment about which approach saves time. As a rough heuristic, if you would need to change more than 30% of the code, regeneration may be faster. Multi-Turn Versus Single-Shot Approaches Generation can happen in a single exchange or across multiple turns of conversation. Each approach has strengths. Single-shot generation provides the specification and receives complete output in one exchange. This works well when: The task is well-defined and moderate in complexity The specification is complete and unambiguous You want to avoid context accumulation from conversation history Multi-turn generation builds output through conversation. This works well when: The task is exploratory and you are discovering requirements You want to review intermediate output before proceeding The full task exceeds what can be reliably generated at once Multi-turn conversations accumulate context, which has both benefits and costs. The AI retains information from earlier in the conversation, which can help with consistency. But it also retains abandoned approaches and corrections, which can create confusion. For complex multi-turn generations, periodically summarize the current state and clear irrelevant history. A hybrid approach often works well: use multi-turn conversation to explore and refine the specification, then use single-shot generation with the finalized specification to produce the actual code. Managing Context Window Limitations Current AI models have context window limits. When your specification, context, and conversation history exceed these limits, information gets truncated. Managing this constraint is part of effective generation. Prioritize recent and relevant information. If truncation occurs, it typically affects earlier content. Structure your prompts so that the most critical information appears in the specification and recent context, not in distant conversation history. Use summarization for long conversations. If a conversation has gone on long enough that relevant information might be truncated, pause and summarize. Create a new message that captures the current state: what has been decided, what the current task is, what constraints apply. This summary becomes the effective context even if earlier messages are truncated. Break large tasks into smaller generations. If a task requires more context than fits in the window, decompose it. Generate components separately with focused context, then integrate them. This keeps each generation within context limits and makes each subtask more tractable. Externalize stable context. System prompts, coding standards, and architectural documentation can be maintained as separate artifacts referenced by the specification rather than included in full. "Follow the error handling conventions documented in CONVENTIONS.md" is more compact than including the full conventions inline. The Discipline of Not Accepting Plausible Output In my post on the unstructured AI problem, I described the "looks reasonable" trap: AI-generated code appears confident and coherent, which makes it easy to accept without sufficient scrutiny. The Generate phase requires active resistance to this trap. Plausible is not correct. Code that looks right and code that is right are different things. The AI produces output that matches patterns in its training data. Those patterns may not match your specific requirements, even if they seem reasonable in isolation. Confidence is not accuracy. The AI presents all output with the same confident tone. It does not signal uncertainty, even when uncertain. Treat all generated code as requiring verification, regardless of how authoritative it sounds. First impressions are unreliable. The initial read of generated code often focuses on whether it looks like code that would work. This is a low bar. The question is not whether the code looks plausible but whether it meets your specification. The discipline is to hold generated code in a tentative state until evaluation confirms its quality. This means resisting the urge to immediately integrate code that seems fine. It means maintaining skepticism even when the AI produces something impressive. It means remembering that generation is the middle of the process, not the end. When Generation Reveals Specification Gaps Sometimes the most valuable output of generation is not the code but the realization that your specification was incomplete. This is not a failure of generation. It is generation working correctly by revealing what you did not know you did not know. Signs that generation has revealed a specification gap: The AI asked clarifying questions you had not considered The generated code handles cases you did not specify The generated approach differs from what you imagined, but you cannot point to a specification requirement it violates You find yourself wanting to add constraints you did not originally include When this happens, return to the Specify phase. Update your specification to address the discovered gap. Then regenerate with the improved specification. This loop between Specify and Generate is normal and valuable. It means the process is surfacing ambiguity at low cost. Do not try to patch specification gaps through prompt iteration alone. If the specification is genuinely incomplete, fix the specification. Prompting around a specification gap creates code that addresses the gap in ad-hoc ways, making evaluation harder and creating inconsistency if similar gaps arise in future tasks. From Generation to Evaluation Generation produces candidate code. That code enters the Evaluate phase, where human judgment determines whether it meets the specification and fits the system. The transition from Generate to Evaluate should be crisp. Do not blur the boundary by informally evaluating during generation and accepting code that "seems good enough." Complete the generation with code that addresses the full specification, then evaluate systematically. In the next two posts, I will explore prompt patterns that make generation more effective. These patterns are reusable approaches to common generation challenges: decomposing complex tasks, teaching by example, constraining output, and more. The patterns build on the foundation of good specification and disciplined generation covered here. For now, practice the disciplines of context management and iteration. Notice when context helps and when it hurts. Notice when iteration is productive and when it is spinning. Notice when you are tempted to accept plausible output before evaluation confirms its quality. The Generate phase is where AI capability becomes visible. Making that capability reliable is the work of ADD. Let's Continue the Conversation What context management strategies have you found effective? What signals tell you that context is helping versus creating noise? How do you decide when to iterate versus when to accept generated output? Share your approaches in the comments. Effective generation is a skill that develops through practice and shared learning. --- # Specification Templates: A Practical Library for AI Development URL: https://www.ivanturkovic.com/2026/01/30/specification-templates-practical-library/ Published: 2026-01-30 Topics: AI, AI-Driven Development In the previous post, I made the case that specification is the highest-leverage skill in AI-driven development. A precise specification produces better output, requires less iteration, and surfaces ambiguity before it becomes a bug. But writing detailed specifications from scratch is cognitively demanding. You must simultaneously consider functional requirements, constraints, context, edge cases, and integration points. Holding all of this in your head while also thinking about the actual problem you are trying to solve is exhausting. Templates solve this problem. A specification template is a reusable structure that prompts you to provide the information AI needs. Instead of remembering what to include, you fill in sections. Instead of inventing structure, you follow a proven format. This post provides a library of specification templates for common development tasks. Each template is complete enough to use immediately and annotated with guidance on customization. By the end, you will have practical tools that make specification-first development sustainable. Why Templates Matter Templates provide three benefits that compound over time. Consistency. When every developer on a team uses the same template structure, specifications become predictable. Code reviewers know where to find constraint information. New team members can read specifications without learning idiosyncratic formats. The AI receives consistently structured input, which improves output consistency. Completeness. A well-designed template includes sections for everything that matters. You cannot forget to specify error handling if the template has an error handling section staring at you. Templates encode the lessons of past omissions. Every time a missing specification element caused problems, that element gets added to the template. Reduced cognitive load. Writing a specification becomes a matter of filling in sections rather than inventing structure. This frees mental energy for the actual thinking: what should this code do, what constraints apply, what could go wrong. The template handles the meta-question of what to specify so you can focus on the object-level questions. Teams that adopt specification templates report that specifications take less time to write, not more. The template eliminates the blank-page problem and provides momentum that carries through to completion. Template Structure Overview Every template in this library follows a common structure with five core sections: Purpose: A one or two sentence summary of what the code should accomplish. This orients the AI and serves as a check that you understand your own goal. Requirements: The functional specifications. Inputs, outputs, behaviors, transformations. What the code must do. Constraints: The boundaries. Technology choices, performance requirements, security requirements, style requirements. What rules the code must follow. Context: The environment. Existing patterns, interfaces, dependencies, examples from the codebase. Where the code lives. Edge Cases and Errors: The exceptional paths. What happens with invalid input, boundary conditions, failure scenarios. How the code should behave when things go wrong. Some templates add domain-specific sections. An API template includes request/response formats. A UI component template includes state management. But the five core sections appear in every template. Template: API Endpoint Use this template when specifying a REST API endpoint, GraphQL resolver, or similar request handler. ## API Endpoint Specification ### Purpose [One sentence: what does this endpoint do and why does it exist?] ### Endpoint Details - Method: [GET/POST/PUT/PATCH/DELETE] - Path: [/api/v1/resources/{id}] - Authentication: [Required/Optional/None, specify method] - Authorization: [What permissions are required?] ### Request - Path parameters: [List with types and validation rules] - Query parameters: [List with types, defaults, validation rules] - Request body: [JSON schema or TypeScript interface] - Headers: [Any required custom headers] ### Response - Success (200/201): [JSON schema or TypeScript interface] - Error responses: - 400 Bad Request: [When and what body] - 401 Unauthorized: [When and what body] - 403 Forbidden: [When and what body] - 404 Not Found: [When and what body] - 422 Unprocessable Entity: [When and what body] - 500 Internal Error: [What body, what gets logged] ### Business Logic [Step-by-step description of what the endpoint does] 1. Validate input 2. Check authorization 3. [Domain-specific steps] 4. Return response ### Constraints - Performance: [Expected latency, throughput requirements] - Rate limiting: [If applicable] - Idempotency: [For POST/PUT, is it idempotent?] - Caching: [Cache headers, invalidation rules] ### Context - Related endpoints: [Links or descriptions] - Database tables: [What tables are read/written] - External services: [What services are called] - Example similar endpoint: [Paste code or link to existing endpoint that follows the right patterns] ### Edge Cases - [Empty result set] - [Maximum page size] - [Concurrent modification] - [Partial failure scenarios] Customization guidance: For internal APIs, you may simplify authentication and authorization sections. For public APIs, expand the error response section with user-friendly messages. For high-throughput endpoints, add detailed performance constraints. Template: Data Transformation Function Use this template when specifying a function that transforms data from one format to another, processes collections, or performs calculations. ## Data Transformation Specification ### Purpose [What data goes in, what data comes out, why is this transformation needed?] ### Function Signature - Name: [function_name] - Parameters: - [param_name]: [type] - [description, valid values] - Returns: [type] - [description] - Raises/Throws: [list of exceptions with conditions] ### Transformation Rules [Precise description of how input maps to output] - [Rule 1: when X, output Y] - [Rule 2: when A, output B] - [Default behavior when no rules match] ### Examples Input: [concrete example] Output: [expected result] Input: [edge case example] Output: [expected result] ### Constraints - Performance: [Time complexity requirements, e.g., must be O(n)] - Memory: [Space constraints, streaming requirements] - Immutability: [Should input be modified or preserved?] - Thread safety: [Concurrent access requirements] ### Context - Called by: [What code calls this function?] - Calls: [What other functions does this use?] - Similar function: [Example from codebase] ### Edge Cases and Errors - Empty input: [Return empty collection / raise error / return default] - Null/None values: [How to handle] - Invalid input: [Validation approach and error messages] - Overflow/underflow: [For numeric operations] Customization guidance: For simple transformations, the examples section may be sufficient without detailed transformation rules. For complex business logic, expand the transformation rules into a comprehensive specification. For performance-critical code, add benchmarking requirements. Template: UI Component Use this template when specifying a React component, Vue component, or similar UI element. ## UI Component Specification ### Purpose [What does this component display? What user need does it serve?] ### Component Interface - Name: [ComponentName] - Props: - [propName]: [type] - [required/optional] - [description] - Events/Callbacks: - [onEvent]: [signature] - [when fired] - Slots/Children: [If applicable] ### Visual Requirements - Layout: [Description or link to design] - Responsive behavior: [Mobile, tablet, desktop variations] - States: - Default: [description] - Loading: [description] - Empty: [description] - Error: [description] - Disabled: [description] ### Interaction Behavior - [Click behavior] - [Hover behavior] - [Focus behavior] - [Keyboard navigation] ### State Management - Internal state: [What state does the component manage?] - External state: [What state comes from props/context/store?] - State transitions: [What causes state changes?] ### Constraints - Accessibility: [ARIA requirements, screen reader behavior] - Performance: [Render optimization, virtualization needs] - Browser support: [Specific requirements] - Dependencies: [Allowed libraries, prohibited libraries] ### Context - Design system: [Link to design system, component library] - Similar component: [Example from codebase] - Parent components: [Where is this component used?] ### Edge Cases - [Very long text content] - [Missing optional props] - [Rapid user interaction] - [Network failure during async operations] Customization guidance: For design system components, expand the visual requirements with design tokens and spacing specifications. For complex interactive components, add a state machine diagram or description. For form components, add validation and submission handling sections. Template: Validation Logic Use this template when specifying validation rules for user input, API payloads, or data integrity checks. ## Validation Specification ### Purpose [What is being validated and why?] ### Input - Type: [What type of data is being validated?] - Source: [Where does this data come from?] ### Validation Rules | Field | Rule | Error Code | Error Message | |-------|------|------------|---------------| | [field] | [rule description] | [CODE] | [User-facing message] | ### Rule Details [For complex rules, provide detailed specifications] Rule: [Rule name] - Condition: [When does this rule apply?] - Logic: [How is validity determined?] - Examples: - Valid: [example] - Invalid: [example] ### Return Value - Success: [What is returned when valid?] - Failure: [Structure of validation errors] ### Constraints - Fail-fast vs. collect-all: [Stop at first error or return all errors?] - Sanitization: [Should input be sanitized as well as validated?] - Async validation: [Any rules requiring external calls?] ### Context - Existing validators: [Reusable validation functions to use] - Error handling pattern: [How errors are displayed to users] ### Edge Cases - [Empty string vs. null vs. undefined] - [Whitespace handling] - [Unicode and special characters] - [Maximum length boundaries] Customization guidance: For form validation, group rules by field and include real-time vs. submission validation distinction. For API validation, include HTTP status codes for different validation failures. For complex business rules, add cross-field validation specifications. Template: Database Migration Use this template when specifying schema changes, data migrations, or database modifications. ## Database Migration Specification ### Purpose [What change is being made and why?] ### Migration Type - [ ] Schema change (DDL) - [ ] Data migration (DML) - [ ] Both ### Changes #### Tables [For each table affected] Table: [table_name] - Action: [CREATE/ALTER/DROP] - Columns: - [column_name]: [type] [constraints] - [ADD/MODIFY/DROP] - Indexes: - [index_name]: [columns] - [ADD/DROP] - Foreign keys: - [constraint_name]: [specification] - [ADD/DROP] #### Data Changes [If data migration] - Source: [Where does data come from?] - Transformation: [How is data modified?] - Destination: [Where does data go?] ### Rollback Plan [How to reverse this migration] - Schema rollback: [DDL to undo] - Data rollback: [Is it possible? How?] ### Constraints - Downtime: [Zero-downtime required? Maintenance window?] - Performance: [Expected duration, table lock implications] - Data preservation: [What must not be lost?] ### Context - Related migrations: [Dependencies, ordering] - Application changes: [Code that must deploy with this migration] - ORM/Framework: [Specific migration tool being used] ### Verification - Pre-migration checks: [What to verify before running] - Post-migration checks: [How to verify success] - Rollback triggers: [When to roll back] Customization guidance: For large table migrations, add batching specifications and progress monitoring. For multi-database environments, specify which databases are affected. For regulated environments, add audit trail requirements. Template: External Service Integration Use this template when specifying code that integrates with third-party APIs, SDKs, or external systems. ## External Service Integration Specification ### Purpose [What capability does this integration provide?] ### Service Details - Service: [Name and version] - Documentation: [Link to API docs] - Authentication: [API key, OAuth, etc.] - Environment: [Different endpoints for dev/staging/prod?] ### Operations [For each operation being implemented] Operation: [operation_name] - HTTP Method: [GET/POST/etc.] - Endpoint: [URL pattern] - Request: [Parameters, headers, body] - Response: [Expected response structure] - Rate limits: [Requests per second/minute/hour] ### Error Handling - Network errors: [Retry strategy, timeout handling] - Rate limiting: [Backoff strategy] - Authentication errors: [Token refresh, re-authentication] - Service errors: [How to handle 5xx responses] - Validation errors: [How to handle 4xx responses] ### Data Mapping - Inbound: [How service data maps to our domain models] - Outbound: [How our data maps to service format] ### Constraints - Timeout: [Maximum wait time] - Circuit breaker: [Failure threshold, recovery time] - Caching: [What responses can be cached, TTL] - Idempotency: [For write operations] ### Context - Wrapper pattern: [Link to existing integration pattern] - Credentials management: [How secrets are stored/accessed] - Monitoring: [What to log, what metrics to emit] ### Testing - Mock strategy: [How to test without hitting real service] - Sandbox environment: [If available] - Test credentials: [How to obtain] Customization guidance: For payment integrations, add PCI compliance requirements. For data-heavy integrations, add pagination handling. For webhook-based integrations, add payload verification and idempotency handling. How to Customize Templates for Your Domain These templates are starting points, not final forms. Your team should customize them based on your technology stack, your patterns, and the failures you have experienced. Add sections for repeated omissions. Every time a missing specification element causes problems, consider adding a section for it. If your team repeatedly forgets to specify logging requirements, add a logging section. If error messages are inconsistent, add an error message section with format guidelines. Remove sections that do not apply. If your team never builds public APIs, simplify the API template by removing extensive error response documentation. If you use a single database, remove multi-database considerations from the migration template. Templates should be right-sized for your context. Include examples from your codebase. The context section of every template has a place for "similar code." Populate this with actual examples from your system. When developers fill out the template, they should include a link or paste code that demonstrates the patterns to follow. Version your templates. As your practices evolve, your templates should evolve. Maintain templates in version control alongside your code. Review and update them periodically based on team feedback and observed results. Evolving Templates Based on Results Templates are living documents. They should improve based on what you learn from using them. After completing an ADD cycle, ask: Did the specification produce good results? If not, why? Was important information missing? Add a section. Was a section routinely unhelpful? Remove or revise it. Track which specification gaps cause the most rework. If a particular type of missing information repeatedly leads to iteration or bugs, that information needs a more prominent place in your templates. Share template improvements across the team. When one developer discovers a useful addition, propagate it to everyone. Templates are institutional knowledge that should be collectively maintained. The goal is templates that, when filled out completely, reliably produce specifications that generate good code on the first attempt. This is achievable, but it requires iteration on the templates themselves, not just on the specifications you write with them. Let's Continue the Conversation Which of these templates is most relevant to your work? What sections would you add based on your experience? Does your team already use specification templates? What has worked well, and what has not? Share your thoughts in the comments. I will incorporate community feedback into the downloadable template library. --- # The Quiet Builders: A History of Introverts in Engineering and What AI Means for the Future URL: https://www.ivanturkovic.com/2026/01/29/introverts-engineering-history-ai-future/ Published: 2026-01-29 Topics: AI, Career & Leadership, Software Engineering Throughout human history, there has always been a place where the quiet ones could excel. A domain where deep thinking mattered more than small talk, where careful analysis outweighed charisma, and where the quality of your work spoke louder than the volume of your voice. That place has been engineering. From the mathematicians of ancient Alexandria to the software architects of Silicon Valley, engineering has served as a sanctuary for introverts. It offered them not just employment, but genuine fulfillment. A space where their natural tendencies toward deep focus, systematic thinking, and solitary problem-solving were not merely tolerated but actively rewarded. Now, as artificial intelligence reshapes how technical work gets done, many introverted engineers are asking a troubling question: Is our safe haven about to disappear? Will AI force us to become salespeople, relationship managers, and perpetual collaborators just to remain relevant? The short answer is no. But the longer answer requires us to understand how we got here in the first place. The Ancient Origins: When Solitary Thinkers Built Civilizations The relationship between introversion and technical work is not a modern phenomenon. It stretches back to the earliest civilizations, where the quiet mastery of numbers, measurements, and construction principles determined who built the structures that lasted millennia. Consider ancient Egypt. While pharaohs commanded armies and priests performed public rituals, a different class of professionals worked in relative obscurity: the scribes and engineers who designed the pyramids. These were individuals who spent years mastering complex mathematical calculations, understanding the properties of stone, and developing the geometric principles that made monumental construction possible. Their work demanded sustained concentration, meticulous attention to detail, and the ability to hold complex systems in their minds. These are classic introvert strengths. The ancient world did not have our modern vocabulary of personality types, but it recognized the distinction between those who thrived in public life and those who excelled in contemplative pursuits. Plato's Academy and Aristotle's Lyceum attracted students who preferred philosophical inquiry to political maneuvering. The great mathematicians of Alexandria, from Euclid to Archimedes, spent their lives in libraries and workshops, not in the agora arguing with merchants. Archimedes himself exemplifies the archetypal introverted engineer. According to historical accounts, he was so absorbed in his mathematical work that he failed to notice the Roman soldiers who had come to arrest him during the siege of Syracuse. His final words, reportedly, were a request not to disturb his circles. This is the behavior of someone whose inner world of ideas was more compelling than external circumstances, even life-threatening ones. The Medieval Period: Monks, Manuscripts, and Mechanical Minds The medieval period might seem like an unlikely chapter in the history of introverted engineering, but it was actually a golden age for those who preferred solitude and deep work. The monasteries of Europe became centers not just of religious devotion but of technical innovation. Monks who copied manuscripts developed increasingly sophisticated techniques for preserving and organizing knowledge. They invented filing systems, indexing methods, and cross-referencing techniques that would later influence library science and, eventually, database design. These were individuals who had chosen a life of contemplation, silence, and routine. In modern terms, they had selected an environment perfectly suited to introverted temperaments. The mechanical innovations of this period also emerged from solitary work. Water mills, windmills, and increasingly complex clockwork mechanisms were designed by craftsmen who spent years perfecting their understanding of gears, leverage, and power transmission. These were not social occupations. They required patience, precision, and the ability to visualize how dozens of components would interact when assembled. The cathedral builders of the Gothic era represent perhaps the most impressive engineering achievements of medieval Europe. While the projects themselves required massive coordination, the master builders who designed them often worked in intellectual isolation, developing geometric techniques for distributing weight, managing thrust, and creating structures that seemed to defy gravity. Their innovations in structural engineering would not be fully understood theoretically until centuries later. The Renaissance and Scientific Revolution: The Rise of the Solitary Genius The Renaissance introduced a new archetype that would persist for centuries: the solitary genius. Leonardo da Vinci filled thousands of pages with engineering designs, anatomical studies, and mechanical inventions, most of which he shared with no one during his lifetime. His notebooks reveal a mind that preferred private exploration to public discourse. Isaac Newton, perhaps the most influential scientist in history, was famously difficult in social situations. He never married, had few close relationships, and spent decades working in isolation on problems in mathematics, physics, and optics. His personality would today be described as deeply introverted, possibly even reclusive. Yet his solitary work transformed human understanding of the physical universe. The pattern repeated across the Scientific Revolution. Many of its leading figures preferred correspondence to conversation, study to socializing, and the company of books to the company of people. This was not seen as a deficiency. It was understood as a temperament well-suited to the demands of natural philosophy and engineering. The technical professions that emerged during this period, from surveying to navigation to instrument-making, all rewarded the introvert skill set. These occupations required precision, patience, and the ability to work independently for extended periods. A surveyor mapping distant territories or an astronomer tracking celestial movements spent more time with their instruments than with other people. The Industrial Revolution: Engineering Becomes a Profession The Industrial Revolution transformed engineering from a collection of crafts and natural philosophies into a recognizable profession. And as it did so, it created unprecedented opportunities for introverts to build successful careers. The great engineers of the 18th and 19th centuries were rarely known for their social graces. James Watt, who perfected the steam engine, was described by contemporaries as reserved and prone to depression. Isambard Kingdom Brunel, who designed bridges, tunnels, and ships that were marvels of their age, was consumed by his work to the exclusion of almost everything else. Nikola Tesla, whose inventions underpin modern electrical systems, was famously eccentric and preferred the company of pigeons to people. These were not exceptions. They represented a type: the engineer as a figure whose genius was inseparable from their preference for solitary, focused work. The profession attracted people who found satisfaction in solving technical problems rather than managing social relationships. The industrial workplace, for all its harshness, offered introverts something valuable: roles where output could be measured objectively. A bridge either stood or it fell. A machine either worked or it didn't. This objectivity provided a refuge from the politics and social maneuvering that dominated other professions. An introverted engineer could let their work speak for itself. The 20th Century: Electronics, Computing, and the Introvert's Paradise If engineering had always been hospitable to introverts, the 20th century made it their promised land. The rise of electronics, telecommunications, and eventually computing created entire industries built around the kind of work introverts do best: complex problem-solving that rewards deep focus and systematic thinking. The early computer industry was populated by people who would later be recognized as extremely introverted. Alan Turing, whose theoretical work laid the foundation for all modern computing, was shy and socially awkward throughout his life. Grace Hopper, who pioneered programming languages, described her preferred working style as solitary and focused. The programmers who wrote the code that sent humans to the moon were not known for their cocktail party skills. As software engineering emerged as a discipline in the 1960s and 1970s, it became perhaps the most introvert-friendly profession in history. Programming required exactly the skills that introverts possessed in abundance: the ability to concentrate for hours, comfort with abstract thinking, tolerance for working alone, and satisfaction derived from solving puzzles rather than from social recognition. The stereotypical programmer of the late 20th century, working alone in a cubicle or basement, communicating more easily with machines than with people, was not just a cultural cliché. It reflected a genuine alignment between personality type and professional requirements. The work demanded introversion, and introverts responded. By the 1990s and 2000s, the technology industry had become the largest employer of introverts in human history. Millions of people who might have struggled in sales, management, or client-facing roles found that they could earn excellent livings by writing code, designing systems, and solving technical problems. The internet made it even better: remote work allowed many engineers to minimize social interaction to whatever level they preferred. The Modern Era: When Collaboration Became Mandatory The first significant challenge to engineering as an introvert sanctuary came not from AI but from changes in how technology companies organized work. Agile methodologies, open-plan offices, pair programming, and constant meetings began to transform the daily experience of software engineering in the 2000s and 2010s. Suddenly, the solitary programmer was considered problematic. Companies wanted "collaborative" engineers who participated enthusiastically in stand-ups, contributed actively in meetings, and worked shoulder-to-shoulder with teammates. The introvert's preference for quiet, focused work was reframed as a lack of team spirit. This shift was difficult for many engineers. The profession they had chosen specifically because it allowed independent work was being transformed into something requiring constant social interaction. Open-plan offices, which research consistently shows reduce productivity and increase stress, became the industry standard. The introvert's need for quiet concentration was overridden by management's desire for visible collaboration. Yet even during this period, introverts found ways to adapt. Remote work, which accelerated dramatically during the pandemic, restored some of the solitude that open offices had eliminated. Asynchronous communication tools like Slack and email allowed introverts to contribute thoughtfully rather than thinking on their feet in meetings. The most successful companies learned that accommodating different working styles produced better results than forcing everyone into the same collaborative mold. The AI Disruption: What's Actually Changing Now we arrive at the present moment, where artificial intelligence is transforming software engineering in ways that seem, at first glance, threatening to introverts. The concern is understandable: if AI can write code, what happens to the professionals whose value lay in their ability to write code better than others? To understand what's actually happening, we need to look at how AI tools are being used in practice, not in marketing materials or breathless predictions. AI coding assistants like GitHub Copilot and Claude can generate code, but they cannot design systems. They can produce implementations, but they cannot evaluate tradeoffs. They can write functions, but they cannot understand why those functions need to exist in the context of a larger product strategy. The work that AI handles well is precisely the work that was already least interesting to experienced engineers: boilerplate, routine implementations, and syntactic details. What AI cannot do is think architecturally. It cannot understand the business context that determines which technical choices make sense. It cannot anticipate how requirements will evolve or how systems will need to scale. It cannot make judgment calls about technical debt, security tradeoffs, or maintainability. These higher-order skills remain firmly in human territory, and they are exactly the skills that experienced, often introverted, engineers have spent careers developing. Why AI Actually Favors Introverted Strengths Here is the insight that most commentary on AI and engineering misses: effective use of AI tools requires exactly the skills that introverts tend to possess. Working with AI is not like managing a team of junior developers, despite the popular analogy. It is more like having a conversation with a very capable but context-free entity. To use AI effectively, you need to think clearly about what you want, articulate it precisely, evaluate the output critically, and iterate thoughtfully. These are solitary, cognitive tasks. They reward reflection, not charisma. The engineers who struggle most with AI tools are often those who relied on collaborative environments to compensate for unclear thinking. If you could always talk through problems with teammates, you might never have developed the ability to formulate problems precisely on your own. AI doesn't let you workshop ideas through conversation. It requires clear input to produce useful output. Introverts, who are accustomed to processing ideas internally before communicating them, often find AI tools more natural to use. They are already practiced at the kind of clear, self-directed thinking that effective AI collaboration requires. Moreover, AI actually reduces some of the social demands that made modern engineering uncomfortable for introverts. Need to implement a feature you've never worked with before? Previously, you might have needed to ask colleagues, schedule meetings, or pair program with someone more experienced. Now, you can explore the problem space with an AI assistant, working at your own pace, in your own way, without social overhead. The Real Threat: Misunderstanding the Transition The genuine risk for introverted engineers is not that AI will eliminate their value. It is that they will misunderstand the transition and position themselves poorly. Some engineers, hearing that AI can write code, conclude that coding skills no longer matter. They begin focusing on soft skills, relationship-building, and other areas where they feel less comfortable. This is a mistake. Deep technical understanding remains essential for directing AI effectively, evaluating its output, and designing systems that AI cannot conceive on its own. Other engineers react to AI by doubling down on low-level implementation details that AI handles well. They resist using AI tools, hoping that pure coding ability will remain valuable. This is also a mistake. The market will increasingly reward engineers who can work effectively with AI, not those who compete against it. The correct response is neither to abandon technical depth nor to ignore AI. It is to develop the judgment, architectural thinking, and strategic perspective that AI cannot replicate, while using AI to handle the implementation details that AI does well. This combination produces engineers who are far more capable than either humans or AI alone. What the Future Actually Looks Like Based on how AI tools are actually being used, and on historical patterns of technological change in engineering, here is what the future likely holds for introverted engineers: First, individual leverage will increase dramatically. A single thoughtful engineer working with AI tools can produce output that previously required a small team. This is good news for introverts, who often prefer to work independently rather than coordinating with large groups. The future may have smaller teams of more senior engineers rather than large teams of mixed experience. Second, the premium on judgment will grow. As AI handles more routine implementation, the distinguishing factor between engineers will increasingly be their judgment about what to build, how to architect it, and when to override AI suggestions. These are skills developed through experience and reflection, not through social networking. Third, written communication will become even more important. Effective AI collaboration requires clear written prompts. Documenting architectural decisions, explaining technical choices, and creating specifications that AI can work from all demand strong writing skills. Introverts, who often prefer written communication to verbal, may have an advantage here. Fourth, deep specialization will remain valuable. While AI has broad but shallow knowledge, engineers with deep expertise in specific domains will be essential for work that requires real understanding. Security engineering, performance optimization, distributed systems design, and other specialized fields will continue to reward depth over breadth. Adapting Without Abandoning Who You Are The advice for introverted engineers navigating the AI transition is not to become extroverts. That advice has never worked and never will. People's fundamental temperaments are remarkably stable across their lifespans. An introvert who forces themselves into an extroverted role typically experiences stress, burnout, and diminished performance. Instead, the goal should be to adapt your working style to new tools while remaining true to your natural strengths. Here is what that looks like in practice: Develop expertise in AI-assisted development. Learn to use AI tools effectively, understanding both their capabilities and their limitations. This knowledge becomes a force multiplier for your existing skills. Focus on architectural thinking. The ability to design systems that meet complex requirements, scale appropriately, and remain maintainable over time is increasingly valuable and distinctly human. This is work that rewards deep thought rather than social interaction. Build a body of written work. Documentation, technical writing, blog posts, and internal knowledge bases all demonstrate expertise without requiring social performance. In the AI era, clear technical writing is more valuable than ever. Find environments that accommodate your working style. Remote work, asynchronous communication, and companies that value results over presence all make engineering more accessible to introverts. The pandemic demonstrated that these arrangements can be highly productive. Cultivate a small network of deep relationships. Introverts typically prefer fewer, deeper connections to many shallow ones. A small network of people who understand and value your work can be more professionally valuable than hundreds of LinkedIn connections. The Historical Pattern Repeats Every major technological transition in the history of engineering has been accompanied by predictions that the need for engineers would diminish. Higher-level programming languages would eliminate the need to understand computing fundamentals. Visual programming tools would let business users build their own applications. No-code platforms would make developers obsolete. None of these predictions came true. What actually happened in each case was that the nature of engineering work shifted to higher levels of abstraction. Engineers stopped writing machine code and started writing in C. They stopped writing C and started writing Python. They stopped building everything from scratch and started assembling systems from cloud services. At each transition, the total demand for engineering talent increased rather than decreased. AI represents another such transition. It will change what engineers do day-to-day, but it will not eliminate the need for people who can think systematically about technical problems, make judgment calls about complex tradeoffs, and design systems that accomplish real-world goals. These capabilities have been at the core of engineering since the pyramids, and they will remain essential for the foreseeable future. The Enduring Value of the Quiet Mind There is something worth celebrating in the long history of introverts finding their place in engineering. It represents a consistent truth about human capability: that deep thought, careful analysis, and solitary focus are genuinely valuable. The world needs both the people who work the rooms and the people who work the problems. From the scribes of ancient Egypt to the programmers of the modern technology industry, introverts have built systems that endure. They have done so not despite their temperament but because of it. The qualities that make social situations draining, the preference for depth over breadth, the need for quiet concentration, these same qualities enable the sustained focus that complex engineering requires. AI does not change this fundamental truth. It changes the tools available, the level of abstraction at which engineers work, and the specific tasks that occupy their time. But it does not change the need for careful, systematic, deep thinking about technical problems. It does not eliminate the value of judgment developed through years of experience. It does not make the quiet mind obsolete. If you are an introverted engineer worried about the AI future, take comfort in history. Your predecessors have weathered many technological transitions, from the invention of the printing press to the creation of the internet. Each time, those who adapted their skills while maintaining their fundamental strengths not only survived but thrived. There is no reason to believe this transition will be different. The sanctuary remains. It has new tools now, new challenges, and new opportunities. But it is still a place where the quiet ones can excel, where deep thinking matters more than small talk, and where the quality of your work speaks louder than the volume of your voice. That has been true for thousands of years. It will continue to be true for many more. Final Thoughts The intersection of personality, technology, and career is something I think about often. As someone who has spent more than two decades building systems and leading technical teams, I've watched many introverted engineers navigate changing industry expectations with grace and resilience. If this perspective resonates with you, I'd encourage you to follow along. I write regularly about the practical realities of software engineering, the strategic implications of AI, and the mindset required to build lasting systems and careers. You can find more at ivanturkovic.com or connect with me directly. I'm always interested in hearing how other engineers are experiencing these transitions. What has your experience been with AI tools? How has your working style evolved over your career? Drop me a message through the contact form or reach out on social media. The best insights often come from conversations with readers who are living these challenges every day. What do you think? Is AI changing the game for introverted engineers, or is this just another transition in a long history of adaptations? --- # Specify: The Most Important Skill in AI-Driven Development URL: https://www.ivanturkovic.com/2026/01/28/specify-the-most-important-skill-in-ai-driven-development/ Published: 2026-01-28 Topics: AI-Driven Development, Software Engineering If you take one thing from this entire series, let it be this: the quality of AI-generated code is bounded by the quality of your specification. No amount of model capability, prompt engineering tricks, or iteration can overcome a vague specification. The ceiling of what AI can produce for you is set by the clarity of what you ask for. This is why specification is the first phase of the ADD cycle, and why I consider it the most important skill in AI-driven development. Everything else depends on getting this right. Why Specification Comes First In unstructured AI usage, specification is often an afterthought. You have a task in mind. You describe it quickly to the AI. You see what comes back. If the output is not quite right, you iterate with follow-up prompts. The specification emerges through conversation rather than preceding it. This approach feels efficient. Why spend time writing detailed specifications when you can just ask and see what happens? The AI is fast. Iteration is cheap. You can course-correct as you go. But this efficiency is illusory. Each iteration costs more than it appears. You must read and evaluate output that may be entirely wrong. You must formulate follow-up prompts that address gaps you only discovered by seeing incorrect output. You accumulate context in the conversation that makes it harder to start fresh if needed. And you often accept "good enough" output because you have already invested time iterating and do not want to start over. More fundamentally, the iterate-until-acceptable approach fails to surface the real problem: you did not know precisely what you wanted. The vagueness was not in your prompt. It was in your thinking. The AI cannot resolve ambiguity you have not resolved yourself. Specification-first inverts this dynamic. Before you generate anything, you articulate exactly what you need. This forces you to think through requirements, constraints, and context before the AI introduces its own assumptions. It surfaces ambiguity early, when addressing it costs nothing but thought, rather than late, when addressing it costs rework. The discipline of specification-first is uncomfortable at first. It feels like unnecessary overhead. But teams that adopt this discipline consistently report that they spend less total time on AI-assisted tasks because they spend less time iterating and reworking. Anatomy of an Effective Specification An effective specification has three components: requirements, constraints, and context. Each serves a distinct purpose, and omitting any of them produces gaps the AI will fill with assumptions. Requirements describe what the code must do. These are the functional specifications: inputs, outputs, behaviors, and transformations. Requirements answer the question "what should this code accomplish?" Effective requirements are specific and testable. "Validate email addresses" is a requirement, but it is not specific enough. Validate according to what standard? What constitutes an invalid email? What should happen when validation fails? Each ambiguity is a place where the AI will make choices you did not authorize. A better requirement might be: "Accept a string input and determine whether it represents a valid email address according to RFC 5321 syntax rules, including support for quoted local parts and domain literal addresses. Return a ValidationResult object containing a boolean isValid field and, when invalid, an errorCode field indicating the specific validation failure." Notice how the better requirement answers questions the vague requirement leaves open. It specifies the standard (RFC 5321), the scope (including quoted local parts and domain literals), the return type (ValidationResult object), and the failure behavior (errorCode for specific failures). The AI now knows what "valid" means in your context. Constraints describe boundaries the code must respect. These are the rules about how the code should be written, not just what it should do. Constraints answer the question "what restrictions apply?" Constraints include technology requirements (use this library, target this Python version), performance requirements (must handle N requests per second, must complete in under M milliseconds), security requirements (must sanitize inputs, must not log sensitive data), and stylistic requirements (follow this naming convention, match this existing pattern). Constraints are often omitted from casual prompts because they feel obvious to the developer. Of course the code should be secure. Of course it should perform well. But "obvious" constraints are not obvious to the AI. It will generate insecure code if security is not specified. It will generate inefficient code if performance is not specified. What you do not state, you leave to chance. I have seen developers frustrated that AI-generated code used a deprecated library when a better alternative existed. But they never told the AI which libraries to use. I have seen developers annoyed that generated code did not match their formatting conventions. But they never provided those conventions. The AI is not being obtuse. It simply has no access to information you have not provided. Context describes the environment the code will operate in. This is information about the larger system: existing patterns, interfaces, dependencies, and integration points. Context answers the question "where does this code live?" Context matters because AI-generated code must fit into an existing system. A function that is correct in isolation may be wrong in context. It may use different error handling than the rest of your codebase. It may duplicate functionality that already exists. It may introduce patterns inconsistent with your architecture. Providing context means showing the AI what your codebase looks like. Include examples of similar functions. Show the interfaces the new code must implement or consume. Describe the architectural patterns you follow. The more context you provide, the better the AI can generate code that fits. Effective context provision is a skill that develops with practice. Early on, you may not know what context matters. As you see how omitted context leads to integration problems, you learn to provide more upfront. The goal is to give the AI enough information to write code that looks like it belongs in your system. The Vague-to-Precise Spectrum Specifications exist on a spectrum from vague to precise. Understanding where your specification falls on this spectrum helps you recognize when more work is needed before generation. At the vague end: "Write a function to process user data." This specification provides almost nothing. What kind of processing? What user data? What format? What output? The AI must invent answers to every important question. The output might technically satisfy the request while being entirely useless for your actual needs. Slightly better: "Write a Python function that takes a user object and validates the email field." This adds language, subject matter, and a specific operation. But it still leaves critical questions unanswered. What is the structure of the user object? What validation rules apply? What happens on success or failure? The AI still has significant room to make choices you might not want. Better still: "Write a Python function that takes a User dataclass (defined in models.py with fields: id, email, name, created_at) and validates the email field using the email-validator library. Return True if valid, raise InvalidEmailError with a descriptive message if invalid." Now we have concrete types, a specific library, and defined behavior for both success and failure. The AI has much less room for unwanted interpretation. At the precise end: A specification that includes the function signature, the validation rules with specific edge cases, the error messages to use, examples of valid and invalid inputs, the relationship to existing code, and any performance or security requirements. Moving along this spectrum requires effort. The more precise your specification, the more thinking you must do before generation. But this thinking is not overhead. It is the actual work of software development. The specification captures decisions that must be made regardless of whether a human or an AI writes the implementation. Bad Specification vs Good Specification Let me illustrate with a concrete example. Suppose you need a function to calculate shipping costs. Bad specification: "Write a function to calculate shipping costs based on package weight and destination." This will produce a function. The function might even work for some cases. But the AI will decide: What weight unit to use. What destinations are valid. How to structure the pricing. What to do with edge cases like zero weight or unknown destinations. What to return and in what format. Whether to include taxes or handling fees. You will receive code built on invisible assumptions that may not match your needs. Good specification: "Write a Python function calculate_shipping_cost with the following specification: Inputs: weight_kg: float, the package weight in kilograms (must be > 0 and <= 70) destination_zone: str, one of 'domestic', 'north_america', 'europe', 'asia', 'other' Output: Decimal representing the shipping cost in USD, rounded to 2 decimal places Pricing rules: Base rate: $5.00 for domestic, $15.00 for north_america, $25.00 for europe, $30.00 for asia, $40.00 for other Per-kg rate: $0.50 for domestic, $2.00 for all international zones Total = base_rate + (weight_kg * per_kg_rate) Error handling: Raise ValueError with message 'Weight must be between 0 and 70 kg' if weight is out of range Raise ValueError with message 'Unknown destination zone: {zone}' if destination_zone is not recognized Additional requirements: Use Decimal for all calculations to avoid floating-point errors Match the pattern used in pricing/domestic.py for consistency Include type hints Do not round intermediate calculations, only the final result" This specification leaves little room for interpretation. The AI knows exactly what to build. You can evaluate the output against specific criteria. Any deviation from the specification is clearly a bug rather than a reasonable interpretation of ambiguous requirements. Specification as a Thinking Tool Writing detailed specifications is not just about communicating with AI. It is a thinking tool that surfaces problems before they become expensive. When you write a specification, you are forced to answer questions you might otherwise defer. What exactly should happen when the input is invalid? The specification requires an answer. What performance characteristics matter? The specification requires you to decide. How does this component interact with existing code? The specification requires you to examine those interfaces. Many developers find that the act of writing a specification reveals gaps in their understanding. "I thought I knew what I needed, but when I tried to write it down, I realized I had not decided how to handle X." This revelation before generation is valuable. Discovering that you do not know what you want is much cheaper before you have generated and partially integrated code based on the wrong assumptions. Specification also serves as documentation. A well-written specification describes not just what the code does but why certain decisions were made. When you or a teammate revisits the code later, the specification provides context that the implementation alone cannot. Teams that practice specification-first often find that their specifications become reusable artifacts. A specification for a common pattern can be adapted for similar tasks. The investment in a good specification pays dividends beyond the immediate generation. Common Specification Failures Even developers who understand the importance of specification make predictable mistakes. Recognizing these patterns helps you avoid them. Implicit constraints. You know your codebase uses a particular error handling pattern, so you do not mention it. The AI generates code with different error handling. You think "obviously it should match our patterns," but you did not say so. Always state constraints explicitly, even when they feel obvious. Missing edge cases. You specify the happy path thoroughly but neglect to mention what should happen with empty inputs, null values, extremely large inputs, or malformed data. The AI will handle these cases somehow, but probably not the way you want. Assumed context. You reference "the User model" without providing its definition, assuming the AI knows your codebase. It does not. Include definitions, examples, or explicit descriptions of anything you reference. Premature implementation details. You specify not just what the code should do but exactly how it should do it, at an algorithmic level. This over-constrains the AI and may produce worse results than letting it choose implementation approaches. Specify outcomes and constraints, not algorithms, unless the algorithm itself is a requirement. Conflicting requirements. You specify that the code should be both maximally performant and maximally readable, without acknowledging that these goals sometimes conflict. When requirements conflict, specify priorities. Missing integration points. You specify the function in isolation without describing how it connects to the rest of the system. The AI produces a correct function that does not fit your architecture. Practical Exercise: Write Specifications for Existing Code Here is an exercise that will sharpen your specification skills: take a function from your codebase and write a specification that would produce it. Choose a function of moderate complexity, something that handles a few edge cases and integrates with other parts of your system. Now imagine you needed to regenerate this function using AI. What specification would you write? As you write, notice what you must include. The obvious functional requirements, yes, but also the subtle constraints: the error handling pattern, the logging approach, the way it interacts with other components. Notice what you might have omitted if you were writing a casual prompt. Now compare your specification to what you would have written before reading this post. Is it more detailed? More explicit? Does it capture decisions that your casual prompt would have left to chance? This exercise builds the mental muscle for specification. The more you practice articulating what code needs to do, the more naturally you will write detailed specifications before generation. From Specification to Generation A good specification sets the stage for effective generation. When you have clearly articulated what you need, the generation phase becomes more focused and more likely to succeed on the first attempt. The relationship between specification quality and generation quality is not linear. A specification that is 80% complete does not produce output that is 80% correct. It produces output that may be entirely wrong in the 20% you did not specify, and that wrongness often infects the parts you did specify. The AI makes choices in the unspecified areas, and those choices constrain what is possible in the specified areas. This is why specification deserves the investment. The return on careful specification is disproportionate to the time invested. But specification alone is not sufficient. The next phase, Generate, involves managing context, selecting appropriate prompt patterns, and iterating effectively when needed. A good specification makes generation easier, but generation has its own disciplines. In the next post, I will provide a library of specification templates for common development tasks. These templates encode the lessons from this post into reusable formats that ensure completeness and consistency. They reduce the cognitive load of specification while maintaining the precision that produces good results. For now, begin practicing specification-first. Before your next AI interaction, pause. Write down what you actually need. Be specific about requirements, constraints, and context. Then generate. The difference in output quality will make the case better than any argument I can offer. Let's Continue the Conversation Try the exercise: write a specification for existing code in your project. What did you discover about the hidden complexity of what you thought was a simple function? What specification failures have you encountered? What happened when the AI filled gaps you did not know existed? Share your experiences in the comments. Specification is a skill that improves with practice, and learning from each other's discoveries accelerates that improvement. --- # From Waterfall to ADD: Why AI Demands Its Own Methodology URL: https://www.ivanturkovic.com/2026/01/27/waterfall-to-add-ai-methodology/ Published: 2026-01-27 Topics: AI, AI-Driven Development, Software Engineering Software development methodologies do not emerge from academic theory or conference talks. They emerge from pain. Practitioners encounter problems that existing approaches cannot solve, and they develop new disciplines to address those problems. Understanding this history matters because AI-assisted development is at an inflection point. The unstructured approaches I described in my previous post are producing real problems: subtle bugs, security vulnerabilities, architectural drift, and skill erosion. These problems will not be solved by better AI models or smarter prompts. They require methodology. To understand why, let us look at how methodologies have emerged before. The Waterfall Era and Its Limitations In the early decades of software engineering, the dominant model was borrowed from manufacturing and construction. You gathered requirements, designed the system, implemented it, tested it, and deployed it. Each phase completed before the next began. Progress flowed downward like water over a falls, hence the name. Waterfall made intuitive sense. It mapped to how physical products were built. You would not start constructing a building before the blueprints were complete. You would not wire the electrical system before the walls were framed. Sequential phases with clear handoffs seemed like the obvious way to manage complexity. For certain projects, waterfall worked adequately. When requirements were stable and well understood, when the problem domain was familiar, when the technology was mature, the sequential approach could deliver results. Government contracts, embedded systems with fixed specifications, and projects with regulatory constraints often succeeded with waterfall methods. But software turned out to be fundamentally different from buildings. Requirements changed mid-project as stakeholders saw early results and refined their understanding. Technology evolved during development cycles. Market conditions shifted. The assumption that you could fully specify a system before building it proved false for most software projects. The pain accumulated. Projects ran over budget and over schedule. Delivered systems failed to meet actual needs because needs had evolved during the long development cycle. Teams spent months implementing features that were obsolete by launch. The feedback loop between implementation and requirements was too slow. This pain created the conditions for change. The Agile Revolution In 2001, seventeen software practitioners gathered at a ski resort in Utah and articulated what many had been discovering independently: the waterfall model did not fit the reality of software development. They produced the Agile Manifesto, a brief document that prioritized individuals and interactions over processes and tools, working software over comprehensive documentation, customer collaboration over contract negotiation, and responding to change over following a plan. Agile was not a single methodology but a family of approaches united by common principles. Scrum introduced sprints and daily standups. Extreme Programming emphasized pair programming and continuous integration. Kanban visualized workflow and limited work in progress. Each variant addressed the core insight differently, but all recognized that software development required shorter feedback loops and greater adaptability than waterfall allowed. The key innovation was embracing change rather than resisting it. Instead of trying to specify everything upfront, agile methods delivered working software incrementally and incorporated feedback continuously. Requirements could evolve because the process expected evolution. Stakeholders could see working software early and adjust direction based on reality rather than speculation. Agile succeeded because it matched how software actually gets built. It acknowledged uncertainty instead of pretending it away. It created structures that turned change from a threat into an opportunity. Teams that adopted agile methods often saw dramatic improvements in delivery speed, stakeholder satisfaction, and developer morale. But agile addressed one set of problems while leaving others unsolved. It improved the relationship between development teams and changing requirements. It did not, by itself, address the internal quality of the code being produced. Test-Driven Development: Inverting the Assumption The traditional approach to testing was straightforward: write the code, then write tests to verify it works. This sequence felt natural. How could you test something that did not exist? You had to build first and verify second. Kent Beck and others recognized a problem with this approach. When tests came after implementation, they were shaped by the implementation. Developers tested what they had built rather than what the system needed to do. Edge cases that the implementation happened to miss were also missed by tests written to verify that implementation. Testing became a confirmation exercise rather than a quality gate. Test-Driven Development inverted the sequence. Write a failing test first. Then write the minimum code to make the test pass. Then refactor to improve the code while keeping tests green. Red, green, refactor. This simple cycle had profound implications. When tests came first, they expressed intent rather than confirming implementation. The test defined what success looked like before any code existed. This forced clarity about requirements at the moment of implementation, not after. It surfaced ambiguity early, when addressing it was cheap, rather than late, when it was expensive. TDD also changed the relationship between developers and their code. The tests provided a safety net for refactoring. You could improve code structure with confidence because the tests would catch regressions. This enabled continuous improvement that was too risky without test coverage. The inversion was counterintuitive but powerful. By changing when tests were written, TDD changed how developers thought about code quality. It embedded quality into the development process rather than treating it as a separate verification step. Behavior-Driven Development: Specifications Humans Can Read TDD improved code quality, but it introduced a new problem. The tests were written in programming languages, which meant non-technical stakeholders could not read them. The specifications embedded in tests were invisible to the people who needed to validate that the right thing was being built. Behavior-Driven Development emerged to address this gap. Dan North and others developed approaches that expressed tests in natural language formats that both technical and non-technical participants could understand. The Gherkin syntax became widely adopted: Given some precondition, When some action occurs, Then some outcome should result. BDD scenarios served double duty. They were specifications that stakeholders could read and validate. They were also executable tests that verified the system behaved as specified. The same artifact served both communication and verification purposes. This mattered because building the thing right and building the right thing are different challenges. TDD helped with the former. BDD helped with the latter by making specifications accessible to everyone involved in defining what the system should do. The Pattern: Reality Outgrows Approaches Looking across this history, a pattern emerges. Methodologies do not appear randomly. They emerge when practitioners recognize that existing approaches no longer fit reality. Waterfall assumed stable requirements. When requirements became dynamic, agile emerged. Code-then-test assumed testing was verification. When testing needed to drive design, TDD emerged. Technical specifications assumed a technical audience. When specifications needed broader accessibility, BDD emerged. Each methodology succeeded by addressing a genuine mismatch between how work was being done and how work needed to be done. Each failed when applied to contexts where the mismatch did not exist or where a different mismatch was more pressing. This pattern tells us something important about AI-assisted development. The question is not whether we need methodology. The question is what mismatch exists between current approaches and current reality. Why AI Fits This Pattern AI-assisted development represents a fundamental change in how code gets written. For the first time, developers routinely generate significant portions of their codebase using tools that produce output they did not directly author. This is not an incremental improvement in tooling. It is a qualitative shift in the development process. The mismatch is clear. Our existing methodologies assume human authorship of code. Code review practices assume a human author who can explain their reasoning. Testing strategies assume human understanding of what was implemented. Quality gates assume human awareness of what choices were made and why. AI-generated code breaks these assumptions. The code arrives without the context that human authorship provides. The AI cannot explain its reasoning in the way a human colleague can. The developer who prompted the generation may not fully understand the implementation they received. As I described in my previous post, unstructured AI usage produces systematic problems: subtle bugs, security vulnerabilities, architectural drift, and skill erosion. These are not random failures. They are predictable consequences of a mismatch between our practices and our tools. Ad-hoc approaches that worked for simpler tools fail for AI because AI introduces new categories of risk. The output looks authoritative but may be unreliable. The generation is effortless but evaluation requires expertise. The capability is high but the reliability is variable. This mismatch demands methodology. Not tips and tricks, not prompt engineering hacks, but a coherent discipline that addresses the specific challenges of AI-assisted development. What Makes Methodologies Succeed or Fail Not every proposed methodology succeeds. The history of software development is littered with approaches that sounded good in theory but failed in practice. Understanding why some methodologies succeed helps us understand what an AI methodology needs to provide. Successful methodologies share several characteristics. They address real pain. Agile succeeded because waterfall was causing real problems that practitioners experienced daily. TDD succeeded because test-after approaches were failing to catch bugs early. Methodologies that solve theoretical problems rather than felt problems do not get adopted. They fit the grain of the work. Methodologies that fight against how work naturally flows require constant effort to maintain. Methodologies that align with natural rhythms become habitual. Agile's short iterations fit how software evolves. TDD's red-green-refactor cycle fits how developers think about implementation. They provide clear practices. Abstract principles are insufficient. Practitioners need concrete actions they can take. Scrum's ceremonies, TDD's cycle, BDD's scenario format all give teams specific things to do. Vague exhortations to "be more careful" do not constitute methodology. They scale from individual to team. The best methodologies work for a single developer on a small project and for large teams on complex systems. They provide value at every scale rather than requiring critical mass to function. They evolve with feedback. Successful methodologies adapt based on experience. Practices that do not work get modified or abandoned. New practices emerge to address discovered gaps. The methodology is a living discipline rather than a fixed doctrine. Positioning ADD as the Natural Evolution AI-Driven Development emerges from this tradition. It addresses the specific mismatch between current AI capabilities and current development practices. It provides concrete practices rather than abstract principles. It fits the natural rhythm of AI-assisted work. The ADD cycle of Specify, Generate, Evaluate, and Integrate maps to how AI collaboration actually works. You begin by clarifying what you need, a specification phase that front-loads the thinking too often skipped in ad-hoc usage. You generate output through AI collaboration, with appropriate context and constraints. You evaluate that output with the rigor it requires, applying human judgment that AI cannot provide. You integrate the validated code into your system with appropriate testing and documentation. Each phase addresses specific failures of unstructured usage. Specification counters vague prompts. Generation with context counters context-free interaction. Evaluation counters the "looks reasonable" trap. Integration counters immediate, unexamined adoption. ADD does not replace existing methodologies. It complements them. Teams practicing agile can apply ADD within their sprints. Teams practicing TDD can integrate ADD with their test-first approach. Teams practicing BDD can use their scenarios as ADD specifications. The methodology layers onto existing practices rather than displacing them. In the posts that follow, I will detail each phase of the ADD cycle. We will examine specification practices that produce reliable generation. We will explore prompt patterns that leverage AI capabilities effectively. We will develop evaluation techniques that catch what casual review misses. We will establish integration practices that maintain system coherence. The goal is not perfection. The goal is discipline that matches the nature of the work. AI-assisted development is powerful but unreliable. ADD provides the structure that turns that power into consistent results. Let's Continue the Conversation How have methodologies shaped your development practice? Have you experienced the pattern of new approaches emerging when old ones no longer fit? What aspects of AI-assisted development feel most in need of structure and discipline? Share your thoughts in the comments. Your experience will help shape how I present the practical elements of ADD in upcoming posts. --- # The Unstructured AI Problem: Why Most Teams Are Using AI Wrong URL: https://www.ivanturkovic.com/2026/01/26/unstructured-ai-problem-teams-using-ai-wrong/ Published: 2026-01-26 Topics: AI, Software Engineering Every developer I know uses AI tools now. Copilot suggestions appear mid-keystroke. ChatGPT tabs stay permanently open. Claude conversations stretch across multiple projects. The adoption curve was vertical, faster than any technology shift I have witnessed in two decades of software engineering. But here is the uncomfortable truth: most of us are using these tools without any real methodology. We treat AI as fancy autocomplete, accepting plausible output and hoping for the best. We have adopted revolutionary capability while keeping amateur practices. This is the unstructured AI problem, and it is quietly undermining our codebases. How Developers Actually Use AI Today Let me describe what I observe in my own work and in conversations with other developers. I am not here to judge; I am here to name what is happening. The typical AI interaction looks something like this: You have a task. You open your AI tool. You describe what you need, often in a single sentence or a rough paragraph. The AI produces something. You glance at it. It looks reasonable. You paste it into your codebase, maybe tweak a variable name or two, and move on. Sometimes you iterate. The output was not quite right, so you add a follow-up prompt. "Actually, can you make it handle null values?" The AI adjusts. You accept the new version. Done. This workflow has three characteristics that define unstructured AI usage. First, the input is vague. We describe what we want in the same casual language we might use with a human colleague who already knows our codebase, our constraints, and our preferences. But the AI knows none of these things. It fills gaps with assumptions, and we rarely notice because the output looks syntactically correct. Consider how differently we would approach a specification for a junior developer. We would explain the context: what the system does, how this piece fits, what patterns we use. We would state constraints: which libraries to use, what performance characteristics matter, what edge cases to handle. We would describe the interfaces: what the inputs look like, what the outputs should be, what errors might occur. With AI, we skip all of this. We write "create a function that validates email addresses" and accept whatever the AI produces. We do not specify whether we need RFC-compliant validation or simple pattern matching. We do not mention whether we need to handle internationalized domain names. We do not explain what should happen with edge cases like empty strings or strings with leading whitespace. The AI fills these gaps with its own assumptions, and we inherit those assumptions without knowing what they are. Second, the evaluation is shallow. We check that the code compiles. We check that it does roughly what we asked. We do not systematically verify edge cases, security implications, performance characteristics, or architectural fit. We trust the AI because its output is confident and coherent. This shallow evaluation often comes down to time pressure. We asked the AI for help because we wanted to move faster. Taking time to carefully evaluate the output feels like it defeats the purpose. So we skim. We look for obvious problems. We check that the happy path works. And we move on. Third, the integration is immediate. Code moves from AI output to production path with minimal intermediate steps. We skip the practices we would apply to our own code or to code from a new team member: careful review, testing against specifications, consideration of system-level impact. When a new team member submits a pull request, we review it carefully. We ask questions about their choices. We suggest alternatives. We ensure it fits our patterns. AI-generated code receives no such scrutiny. It arrives pre-formatted, pre-commented, and seemingly complete. The very polish that makes it easy to accept is what makes us skip the steps that would reveal its flaws. This is not carelessness. It is the natural result of tools that are easy to use and produce output that appears correct. The path of least resistance leads directly to unstructured usage. The "Looks Reasonable" Trap AI-generated code has a dangerous property: it almost always looks reasonable. When a human writes problematic code, it often shows visible signs of confusion. Awkward naming. Commented-out experiments. Inconsistent patterns. These signals invite scrutiny. We know to look more carefully when code looks uncertain. AI-generated code carries no such signals. It presents with uniform confidence. Variable names are sensible. Structure is clean. Comments, when present, are articulate. The code reads like it was written by someone who knew exactly what they were doing. This creates a psychological trap. Our brains use fluency as a proxy for correctness. When something is easy to read and understand, we assume it is more likely to be right. This heuristic serves us well in most contexts, but it fails catastrophically with AI-generated code. I have seen AI produce beautifully structured functions that silently fail on boundary conditions. I have seen it generate elegant API handlers with subtle security vulnerabilities. I have seen it write clear, readable algorithms that are O(n²) when the problem demands O(n). In each case, the code looked reasonable. It looked like code I might have written myself. And that appearance of competence made it harder to catch the underlying problems. The "looks reasonable" trap is especially dangerous because it compounds. Each piece of AI-generated code that passes casual review reinforces the habit of casual review. We build confidence in a process that does not deserve our confidence. Hidden Costs That Emerge Over Time Unstructured AI usage does not produce immediate disasters. If it did, we would stop. Instead, it produces slow accumulation of problems that become visible only in retrospect. Subtle bugs that escape initial testing. AI-generated code often handles the happy path correctly while mishandling edge cases. These bugs may not surface for weeks or months, emerging only when specific conditions align. By then, the code has been integrated, built upon, and is expensive to change. Tracking the bug back to its source reveals a piece of AI-generated code that was never rigorously evaluated. Security vulnerabilities that await discovery. AI models are trained on vast amounts of code, including code with security flaws. They reproduce patterns without understanding their security implications. A function that "looks reasonable" might use deprecated cryptographic approaches, fail to sanitize inputs properly, or expose timing vulnerabilities. These issues hide in plain sight until a security audit or, worse, an actual breach exposes them. Architectural drift that accumulates invisibly. Each piece of AI-generated code brings its own implicit assumptions about structure, patterns, and conventions. When these assumptions differ from your codebase's architecture, the drift is rarely dramatic enough to catch attention. But over dozens of AI-assisted additions, your codebase becomes inconsistent. Patterns that should be uniform are implemented three different ways. Abstractions that should be shared are reinvented repeatedly. The system becomes harder to understand and maintain, not because any single addition was wrong, but because the additions were not coordinated. Technical debt that defies measurement. Traditional technical debt comes with awareness. You know when you are cutting a corner. You might even leave a TODO comment. AI-generated technical debt is invisible because you never knew the corner was being cut. The AI produces code that works but is not optimal: inefficient data structures, unnecessarily complex logic, or approaches that will scale poorly. You accept this debt without knowing it exists, and you carry it forward with each build. Skill erosion that happens without notice. In my first post in this series, I discussed how relying on AI for implementation erodes the skills needed to evaluate AI output. This is perhaps the most insidious hidden cost. The more you use unstructured AI assistance, the less capable you become of catching its mistakes. Your evaluation abilities atrophy precisely when you need them most. These costs do not announce themselves. They accumulate quietly, manifesting as systems that are harder to maintain, harder to extend, and harder to trust. By the time the costs become visible, they are deeply embedded. The Gap Between Capability and Reliability Here is a distinction that unstructured AI usage fails to make: AI tools are highly capable but not highly reliable. Capability means the AI can produce correct, useful output for a given task. Current AI tools have remarkable capability. They can write functioning code in dozens of languages. They can implement complex algorithms. They can generate tests, documentation, and infrastructure configurations. The ceiling of what they can do is impressively high. Reliability means the AI consistently produces correct output across varied conditions. This is where current AI tools fall short. The same prompt might produce excellent code on one attempt and subtly flawed code on another. A task that worked perfectly yesterday might fail strangely today. The AI does not know when it is uncertain, and it cannot reliably distinguish between tasks it can handle well and tasks where it will struggle. Unstructured AI usage treats capability as if it were reliability. Because the AI can produce correct code, we assume it will produce correct code. We skip verification because verification seems redundant when the tool is so capable. This is the fundamental category error. Capability tells you what is possible. Reliability tells you what to expect. Professional software development requires reliability. We cannot ship code that might be correct. We need code that we have verified to be correct. The gap between capability and reliability is not a temporary limitation that will disappear with the next model release. It reflects something deeper about how these systems work. AI models generate plausible output based on patterns in training data. They do not have access to ground truth about your specific requirements, your specific codebase, or your specific constraints. They cannot verify their own output against your actual needs. Think about what the AI actually has access to when it generates code for you. It has your prompt, which is typically incomplete. It has whatever context you provided, which is typically minimal. It has patterns learned from millions of code examples, most of which have no relationship to your system. What it does not have is understanding. It does not know why your system is architected the way it is. It does not know which constraints are load-bearing and which are historical accidents. It does not know which parts of your codebase are well-tested and which are fragile. The AI generates code that is statistically similar to correct code. Most of the time, statistically similar is close enough to be useful. But most of the time is not the same as reliable. Reliability requires understanding, and understanding requires context that the AI simply does not possess. This gap must be bridged by human judgment and structured process. That bridging is exactly what unstructured AI usage omits. Why Ad-Hoc Worked Before and Fails Now Developers have always used external resources to help write code. Stack Overflow answers. Documentation examples. Code from other projects. Library sample code. We copy, adapt, and integrate code from outside sources constantly. For these sources, ad-hoc usage worked reasonably well. Why does it fail for AI? The key difference is scope and confidence. When you copy code from Stack Overflow, you typically copy small, focused snippets. A regex pattern. A configuration block. A utility function. The snippet solves a narrow problem, and its narrowness makes it easier to evaluate. You can read five lines of code and understand what they do. AI-generated code can be arbitrarily large and complex. A single prompt might produce dozens or hundreds of lines. The scope of what you need to evaluate expands dramatically, while the time you allocate to evaluation does not expand to match. When you copy code from documentation or sample projects, that code has been written for explanation. It is explicit about its assumptions. It often includes comments describing limitations or requirements. The code signals its own context. AI-generated code comes with no such signals. It looks like production code, not like example code. It presents its assumptions implicitly, buried in implementation choices rather than stated explicitly. You must infer what the AI assumed, and those inferences are easy to miss. When you adapt code from other sources, you typically recognize it as foreign. You know it came from elsewhere. This awareness triggers scrutiny. You mentally flag the code as "needs review" because it was not written with your specific context in mind. AI-generated code triggers no such flag. It arrives in response to your prompt, addressing your described need. It feels like it was written for you, even though the AI has no real understanding of your situation. The appearance of customization suppresses the scrutiny that foreign code would receive. There is also a psychological dimension. When you copy code from Stack Overflow, you know you are borrowing. You maintain an appropriate humility about the borrowed code. AI-generated code feels collaborative, almost co-authored. You described the problem; the AI wrote the solution. This sense of partnership creates false confidence. You feel like you understand the code because you participated in its creation, even though your participation was limited to describing the desired outcome. These differences mean that practices which were adequate for other external code sources are inadequate for AI-generated code. The scope is larger, the signals are weaker, and the apparent customization is deceiving. We need new practices for this new source of code. Setting Up the Need for Methodology I have spent this post diagnosing the problem. Let me preview the direction of the solution. What we need is methodology: a structured approach that matches the nature of AI-assisted development. Not tips and tricks. Not prompt engineering hacks. A coherent discipline that addresses the gap between AI capability and AI reliability. This methodology must do several things. It must front-load clarity. If vague input produces unreliable output, we need practices that ensure our input is not vague. We need specification disciplines that surface ambiguity before generation, not after. It must enforce evaluation. If the "looks reasonable" trap leads to accepting flawed code, we need systematic evaluation practices that catch what casual review misses. We need checklists, heuristics, and habits that apply human judgment consistently. It must structure integration. If immediate integration bypasses quality gates, we need defined checkpoints that code must pass before reaching production. We need the same rigor we would apply to any other high-risk addition to our codebase. It must maintain skills. If AI usage erodes evaluation capacity, we need deliberate practices that preserve and develop those skills. We need to remain capable of the judgment that methodology demands. In the next post, I will trace the history of how methodologies emerge when existing approaches no longer fit reality. Waterfall gave way to Agile. Test-Driven Development inverted assumptions about when tests should be written. Each methodology emerged because practitioners recognized that new conditions demanded new discipline. AI-assisted development is such a condition. The methodology I call ADD (AI-Driven Development) is the discipline it demands. Let's Continue the Conversation I am curious about your experience: Have you noticed the "looks reasonable" trap in your own work? What hidden costs have emerged from AI-generated code in your projects? What evaluation practices, if any, have you developed for AI-generated code? Share your thoughts in the comments or reach out directly. These observations from practicing developers will shape how I present the methodology in upcoming posts. --- # ADD: AI-Driven Development as a Methodology for the Future Engineer URL: https://www.ivanturkovic.com/2026/01/25/ai-driven-development-add-methodology/ Published: 2026-01-25 Topics: AI, AI-Driven Development, Software Engineering Software development has always evolved through methodologies that structure how we think about building systems. Waterfall gave way to Agile. Test-Driven Development changed how we approach correctness. Behavior-Driven Development shifted focus toward specifications that non-technical stakeholders could understand. Each methodology emerged because the existing approaches no longer fit the reality of how software was actually being built. We are at another such inflection point. AI-assisted coding is no longer experimental. It is production reality for millions of developers. Yet most teams are using these powerful tools without any coherent methodology, treating AI as a faster autocomplete rather than a fundamentally different way of building software. This article introduces AI-Driven Development (ADD), a methodology for structuring how engineers work with AI throughout the software development lifecycle. Like TDD or BDD before it, ADD is not about the tools themselves but about the discipline of using them effectively. What AI-Driven Development Is (and Is Not) AI-Driven Development is a structured approach where AI serves as an active collaborator throughout the development process, with humans providing direction, evaluation, and quality control at defined checkpoints. It treats AI not as a code generator to be invoked occasionally but as a persistent development partner whose output requires systematic oversight. ADD is not about removing humans from the loop. It is about redefining where human attention adds the most value. Instead of spending cognitive effort on implementation details, engineers focus on specification, evaluation, and architectural coherence. The methodology creates explicit practices for each of these activities. Think of the relationship between ADD and traditional development like the relationship between directing a film and operating a camera. Both require skill. Both are essential to the final product. But they involve fundamentally different types of attention and expertise. The ADD Cycle: Specify, Generate, Evaluate, Integrate Just as TDD follows a red-green-refactor cycle, ADD operates through a repeatable pattern that structures the collaboration between human and AI. The cycle has four phases: Specify, Generate, Evaluate, and Integrate. Phase 1: Specify Before any code generation, the engineer creates a precise specification of what needs to be built. This is the most critical phase and where ADD diverges most sharply from ad-hoc AI usage. A specification in ADD includes several components. First, the functional requirements: what the code should do, including inputs, outputs, and expected behaviors. Second, the constraints: what patterns to follow, what libraries to use (or avoid), performance requirements, and security considerations. Third, the context: how this component fits into the larger system, what interfaces it must respect, and what assumptions it can make about its environment. Poor specifications produce poor results. "Write a function to process user data" is not a specification. "Write a Python function that validates user registration data, checking email format, password strength (minimum 12 characters, at least one uppercase, one number, one special character), and age (must be 18 or older). Return a ValidationResult object with success status and list of error messages. Raise ValueError for null inputs." is a specification. The discipline of writing precise specifications forces clarity of thought before implementation begins. Many bugs and architectural mistakes originate from vague requirements that get resolved arbitrarily during coding. ADD surfaces these ambiguities early, when they are cheapest to address. Phase 2: Generate With a clear specification in hand, the engineer prompts the AI to generate an implementation. This phase is where most developers start when using AI tools, but in ADD it comes only after the specification work is complete. The generation phase may involve multiple iterations. Initial output might reveal gaps in the specification that require refinement. The AI might ask clarifying questions (if using a conversational interface) or produce code that exposes unstated assumptions. These are signals to loop back to the Specify phase before proceeding. Effective generation also involves context management. Modern AI tools have limited context windows. The engineer must decide what information to include: relevant existing code, interface definitions, examples of similar patterns used elsewhere in the codebase, test cases the implementation must satisfy. Providing too little context produces generic code that does not fit the system. Providing too much creates noise that dilutes focus. A key discipline in this phase is resisting the temptation to immediately accept output that looks plausible. Generation is not the end of the process. It is the beginning of evaluation. Phase 3: Evaluate The evaluation phase is where human judgment becomes essential. AI-generated code can be syntactically correct, pass basic tests, and still be fundamentally wrong for the context. Evaluation is the quality gate that catches these failures. Evaluation in ADD operates at multiple levels. Correctness: does the code actually implement the specification? This includes not just the happy path but edge cases, error handling, and boundary conditions. Fitness: does the code fit the existing system? This covers coding style, naming conventions, error patterns, and architectural consistency. Security: does the code introduce vulnerabilities? AI models can generate code with injection flaws, improper input validation, or insecure defaults. Performance: will the code perform acceptably at expected scale? AI often optimizes for readability over efficiency, which may or may not be appropriate. Evaluation requires the skills discussed in the previous article on future engineering competencies. You cannot evaluate what you do not understand. Engineers who lack the ability to read and reason about code will struggle in this phase, regardless of how good their specifications are. When evaluation reveals problems, the cycle loops back. Minor issues might require regeneration with a refined prompt. Major issues might indicate specification gaps that need addressing. Fundamental mismatches might require human implementation of specific components where AI assistance is not appropriate. Phase 4: Integrate Code that passes evaluation moves to integration with the broader system. This phase includes activities that extend beyond the AI-assisted work itself: writing or updating tests, ensuring documentation reflects the new functionality, verifying that CI/CD pipelines pass, and validating integration with dependent components. Integration also involves a second round of review, this time focused on how the new code affects the system as a whole rather than whether the code itself is correct. Does the component integrate cleanly with existing interfaces? Are there performance implications at the system level? Does the addition create technical debt that needs to be tracked? The integrate phase completes the cycle for one unit of work. Complex features involve multiple cycles, with each cycle building on the previous. The cumulative result is a system built through structured human-AI collaboration rather than either pure AI generation or pure human implementation. Parallels with TDD and BDD Understanding ADD benefits from comparison with methodologies that developers already know. The parallels with Test-Driven Development are particularly instructive. TDD inverts the traditional write-code-then-test approach. You write tests first, watch them fail, then write code to make them pass. This inversion forces clarity about requirements before implementation begins and creates a safety net for refactoring. ADD similarly inverts the traditional relationship with tools. Instead of writing code and occasionally asking AI for help, you start with specifications that guide AI generation, then apply human judgment to evaluate and refine. The specification-first approach forces the same clarity that test-first provides, while the evaluation phase creates the quality gate that testing provides in TDD. ADD and TDD are not mutually exclusive. They complement each other. Specifications in ADD can include test cases that the generated code must satisfy. The TDD cycle can operate within the ADD evaluation phase, with tests validating AI-generated implementations before integration. Teams already practicing TDD may find ADD a natural extension of their existing discipline. The parallels with Behavior-Driven Development are equally relevant. BDD emphasizes specifications written in a language that non-technical stakeholders can understand, typically using Given-When-Then syntax. This focus on human-readable specifications aligns perfectly with ADD's emphasis on precise, communicable requirements. In fact, BDD specifications can serve directly as ADD specifications. A well-written Gherkin scenario contains exactly the information AI needs to generate an implementation: the preconditions, the action, and the expected outcome. Teams already using BDD have a head start on ADD adoption. Foundational Practices for AI-Driven Development Beyond the core cycle, ADD involves several supporting practices that make the methodology effective at scale. Specification Templates Consistent specification formats reduce cognitive overhead and ensure completeness. Teams practicing ADD develop templates for common specification types: API endpoints, data transformations, UI components, validation logic, integration code. A specification template for an API endpoint might include: HTTP method and path, request body schema with field descriptions and validation rules, response schemas for success and error cases, authentication and authorization requirements, rate limiting considerations, and examples of valid and invalid requests. Templates evolve as teams learn what information leads to better generated code. They become a form of institutional knowledge about effective human-AI collaboration. Context Libraries AI generates better code when it understands the context. Teams practicing ADD build libraries of context that can be included with specifications: coding style guides, architectural decision records, interface definitions, examples of patterns used in the codebase. Context libraries require curation. Including everything creates noise. Including too little produces generic output. Effective context libraries are organized by domain and concern, allowing engineers to include precisely the context relevant to a given task. Some teams maintain a "system prompt" document that establishes baseline context for all AI interactions: the technology stack, fundamental patterns, naming conventions, and non-negotiable constraints. This ensures consistency even when individual engineers provide different task-specific context. Evaluation Checklists Systematic evaluation requires systematic practices. ADD teams develop checklists that ensure consistent review of AI-generated code. These checklists codify what "good" looks like for the team and the codebase. A basic evaluation checklist might cover: specification compliance (does it do what was asked?), error handling (are failure modes addressed?), security considerations (input validation, authentication checks, injection prevention), performance implications (algorithmic complexity, resource usage), test coverage (are tests present and meaningful?), documentation (is the code self-documenting or does it need comments?), and integration fit (does it follow established patterns?). Checklists should be living documents that evolve as the team encounters new categories of issues. When evaluation catches a problem, consider whether a checklist item could prevent similar issues in the future. Prompt Patterns Just as software development has design patterns, ADD has prompt patterns: reusable approaches to structuring AI interactions that reliably produce good results. The Decomposition Pattern breaks complex tasks into smaller, well-defined subtasks that can each go through the ADD cycle independently. This mirrors the software engineering principle of separation of concerns and produces more manageable, reviewable output. The Exemplar Pattern provides examples of similar implementations from the codebase, asking the AI to follow the same patterns. This produces code that fits the existing system more naturally than generic generation. The Constraint Pattern explicitly states what not to do: avoid certain libraries, do not use specific patterns, maintain backward compatibility with existing interfaces. Negative constraints often clarify requirements more effectively than positive ones. The Iterative Refinement Pattern treats initial generation as a draft to be refined through conversation. Rather than trying to specify everything upfront, it uses the AI's output to identify gaps and progressively improves the result. Teams build libraries of prompt patterns that work for their context, sharing effective approaches and retiring those that consistently produce poor results. Failure Documentation Not every ADD cycle succeeds. Sometimes AI-generated code fails evaluation repeatedly. Sometimes the task is simply not well-suited to AI assistance. Documenting these failures creates valuable learning. Failure documentation captures: what was attempted, why it failed, whether specification improvements might help, and whether the task should be flagged as unsuitable for AI assistance. Over time, this documentation reveals patterns about where AI helps and where it does not, informing better task allocation decisions. Getting Started with ADD Adopting ADD does not require wholesale transformation of existing practices. Teams can begin with small experiments and expand as they build competence. Start with Well-Bounded Tasks The best initial candidates for ADD are tasks with clear boundaries: utility functions, data transformations, CRUD operations, validation logic, test generation. These have well-defined inputs and outputs, making both specification and evaluation straightforward. Avoid starting with tasks that require deep system understanding or involve complex state management. These are harder to specify and harder to evaluate, making them poor choices for learning the methodology. Practice Specification Writing Specification quality determines outcome quality. Before focusing on AI tools, practice writing precise specifications. Take existing code and write specifications that would reproduce it. Notice what information is necessary and what can be inferred. This exercise builds the muscle memory of specification thinking. It also reveals how much implicit knowledge goes into code that seems simple. Making that knowledge explicit is the core skill of ADD. Establish Evaluation Discipline The natural temptation is to accept AI output that looks reasonable. Fight this temptation. Establish a personal rule: no AI-generated code enters the codebase without explicit evaluation against the specification. This might feel slow initially, but it prevents the accumulation of subtle issues that compound over time. More importantly, it builds evaluation skills that become faster and more reliable with practice. Build Context Incrementally Start with minimal context and add more as you learn what improves output quality. Keep notes on what context helped and what created noise. This empirical approach builds understanding of effective context management for your specific codebase and tools. Measure and Iterate Track how ADD affects your work. Metrics might include: time from specification to integrated code, number of evaluation iterations per task, defects found in AI-generated code after integration, and subjective assessment of code quality. These measurements inform methodology refinement. If certain task types consistently require many iterations, investigate whether better specifications or different prompt patterns could help. If defects cluster in specific areas, strengthen evaluation checklists accordingly. When ADD Is Not the Right Approach No methodology is universal. ADD is powerful but not always appropriate. Tasks requiring deep innovation may not benefit from ADD. AI generates based on patterns in its training data. Novel algorithms, unprecedented architectures, or solutions to genuinely new problems may require human creativity that specification-driven generation cannot provide. Highly stateful or context-dependent code can be difficult to specify completely. Systems where behavior depends heavily on runtime state, complex integrations, or emergent properties may produce specifications so long that they approach the complexity of the implementation itself. Learning situations may warrant traditional development. When the goal is building understanding rather than producing code, working through implementation manually may be more valuable than delegating to AI. This connects to the AI sabbatical concept discussed in the previous article. Security-critical code may require extra caution. While ADD includes security evaluation, some contexts demand implementation by humans with specific security expertise rather than AI generation with human review. The judgment about when to use ADD versus traditional development is itself a skill that develops with practice. Start conservatively and expand scope as confidence grows. The Future of Development Methodologies ADD is not the final word on AI-assisted development. As AI capabilities evolve, methodologies will evolve with them. What remains constant is the need for structured approaches that harness powerful tools effectively. The developers who thrive will be those who think methodologically about their work, treating AI integration as a discipline to be mastered rather than a feature to be used. ADD provides a starting framework for that discipline, one that will undoubtedly be refined and extended as the field matures. For now, the foundations are clear: specify precisely, generate with appropriate context, evaluate rigorously, and integrate carefully. These principles hold regardless of which AI tools you use or how capable they become. They are the habits of effective human-AI collaboration. Let's Continue the Conversation AI-Driven Development is still emerging as a practice. I have shared the framework as I understand it, but the methodology will evolve as more teams experiment and share what works. Are you already practicing something like ADD? What specification or evaluation practices have you found effective? Where have you seen AI-assisted development succeed or fail? Feel free to reach out through my contact page or connect with me on LinkedIn. If you found this framework useful, consider following along for more thoughts on software engineering, AI, and the craft of building things that last. --- # The Future Engineer: What Software Development Looks Like When AI Handles the Code URL: https://www.ivanturkovic.com/2026/01/24/future-engineer-ai-software-development-skills/ Published: 2026-01-24 Topics: AI, Career & Leadership, Software Engineering The software industry has entered a period of genuine transformation. After decades of incremental tooling improvements, AI-assisted development is introducing changes that feel qualitatively different from what came before. Code completion, automated testing, and intelligent refactoring are no longer experimental features but daily realities for many developers. This shift raises uncomfortable questions about the future of software engineering as a profession. What happens to the value of deep technical expertise when AI can generate working code in seconds? How do engineers maintain their skills when the machine handles an increasing share of implementation work? And perhaps most importantly, what does it actually mean to be a software engineer when the nature of the work itself is changing? Having spent more than two decades building and scaling software systems, I find myself thinking about these questions differently than many of the breathless predictions suggest. The answer is neither the utopian vision of effortless productivity nor the dystopian fear of mass obsolescence. The reality is more interesting and more demanding. The Shift from Implementation to Orchestration For most of software engineering's history, the job has been fundamentally about implementation. You understand a problem, design a solution, and then spend the majority of your time translating that solution into working code. Syntax, language idioms, framework conventions, and API quirks consume most of your attention. AI changes this equation by compressing the implementation phase. Tasks that once required hours of careful coding can now be accomplished in minutes with the right prompts and oversight. This compression does not eliminate the need for engineering judgment. Instead, it concentrates it. The future engineer is less a writer of code and more an orchestrator of systems. The role shifts toward higher-level concerns: defining requirements precisely enough that AI can implement them correctly, evaluating generated solutions against architectural constraints, identifying subtle bugs that automated testing misses, and maintaining coherence across complex systems where AI handles individual components. This is not a demotion. Orchestration at scale requires deeper understanding, not less. You cannot effectively direct an AI assistant if you lack the judgment to evaluate its output. The bar for what constitutes competent engineering actually rises. The Skills That Will Define Future Engineers If implementation becomes less central to the engineering role, what skills move to the foreground? Based on patterns I observe in effective AI-augmented development, several capabilities stand out. System Design and Architectural Thinking AI excels at generating code within well-defined boundaries. It struggles with the upstream decisions that define those boundaries: choosing appropriate architectural patterns, identifying the right abstractions, managing complexity across component boundaries, and anticipating how systems evolve under changing requirements. These decisions require understanding trade-offs that span technical domains, business constraints, and operational realities. An engineer who can articulate clear architectural principles and translate them into actionable guidance for AI assistants will be far more effective than one who simply prompts for code without a coherent design vision. Precise Problem Specification The quality of AI-generated code depends heavily on the quality of the input. Vague requirements produce vague solutions. The ability to decompose complex problems into precise, implementable specifications becomes a core engineering skill. This is harder than it sounds. Most developers have learned to work with ambiguous requirements because they resolve ambiguities themselves during implementation. When AI handles implementation, those ambiguities manifest as bugs, unexpected behaviors, or solutions that solve the wrong problem. The engineer who can think clearly about requirements (including edge cases, error conditions, and implicit constraints) will produce better results with less iteration. Critical Evaluation and Quality Judgment AI-generated code can be superficially correct while harboring subtle problems. Security vulnerabilities, performance issues, maintainability concerns, and conceptual mismatches with the broader system often escape automated validation but become obvious to experienced engineers. The future engineer needs well-calibrated judgment about code quality. This means understanding not just whether code works but whether it works well, whether it handles failure gracefully, whether it integrates cleanly with existing systems, and whether it will be comprehensible to future maintainers. AI assistance amplifies productivity only when paired with this kind of evaluative capacity. Cross-Domain Fluency When AI reduces the friction of working across technology stacks, the artificial boundaries between frontend, backend, mobile, and infrastructure become less meaningful. The engineer who understands the constraints and patterns of multiple domains can own problems end-to-end rather than handing off between specialists. This does not mean becoming a shallow generalist. It means developing deep expertise in one area while maintaining working knowledge of adjacent domains. You need enough understanding of frontend rendering to evaluate AI-generated components, enough knowledge of database design to catch problematic queries, and enough operational awareness to anticipate deployment issues, even if you would not claim mastery of all these areas. Effective AI Collaboration Using AI tools effectively is itself a skill that can be developed. This includes understanding how different models behave, knowing when to provide more context versus being more specific, recognizing when AI is confidently wrong, and developing intuition for which tasks AI handles well versus which require human attention. Engineers who treat AI as a junior colleague (capable of handling defined tasks but requiring oversight and guidance) tend to get better results than those who either dismiss AI entirely or trust it uncritically. The relationship is collaborative, and like any collaboration, it improves with practice and understanding of each party's strengths and limitations. Maintaining Deep Technical Understanding Perhaps the most counterintuitive challenge facing future engineers is maintaining deep technical skills when AI handles more implementation work. If you spend less time writing code, how do you preserve the expertise that allows you to evaluate code effectively? This is a genuine problem without easy solutions, but several approaches can help. Planned AI Sabbaticals: The Necessary Discipline Here is an uncomfortable truth that few want to acknowledge: excessive reliance on AI assistance will gradually erode the very skills that make engineers capable of overseeing AI output. The more you outsource implementation to AI, the less equipped you become to evaluate whether that implementation is sound. Without intervention, this creates a dangerous feedback loop where engineers become increasingly dependent on tools they are decreasingly qualified to supervise. Experienced developers with a decade or more of hands-on coding have an initial advantage in this transition. They built their intuitions through years of debugging, refactoring, and wrestling with complex systems. That accumulated judgment does not disappear overnight. But it will atrophy without deliberate maintenance. The solution is structured time away from AI assistance. This is not about rejecting useful tools. It is about preserving the foundation that makes those tools valuable in the first place. Consider implementing regular AI sabbaticals at multiple scales. A weekly practice might involve designating one day where you write code without AI assistance, forcing yourself to recall syntax, debug manually, and work through problems without automated help. This keeps fundamental skills from rusting and maintains your ability to recognize when AI suggestions are subtly wrong. On a quarterly basis, take on a small project or substantial feature entirely without AI. This longer engagement exercises deeper problem-solving muscles: architectural decisions, debugging complex interactions, and the kind of sustained focus that AI-assisted work rarely demands. Treat it as training that maintains your professional capacity. Some organizations may eventually formalize this. Just as companies mandate security training or professional development time, forward-thinking engineering teams might require periodic AI-free work to ensure their engineers retain the judgment necessary for quality oversight. The engineers who maintain this discipline independently will be better positioned than those who let their skills decay unchecked. The goal is not nostalgia for pre-AI development. The goal is ensuring that humans remain genuinely capable of the oversight role that AI-augmented work requires. An engineer who cannot code without AI assistance is not an orchestrator. They are a prompt writer hoping the machine gets it right. Deliberate Practice Beyond Production Work Athletes do not maintain their skills solely through competition. They practice fundamentals deliberately, even when those fundamentals are well-established. Engineers can adopt a similar mindset by setting aside time for hands-on coding without AI assistance. This might mean solving algorithmic problems, implementing known patterns from scratch, or building small projects in unfamiliar languages. The goal is not productivity but skill maintenance: keeping mental models sharp and intuitions calibrated. Some engineers dedicate a few hours weekly to this kind of deliberate practice. Deep Review of AI-Generated Code Rather than treating AI output as a black box, use it as a learning opportunity. When AI generates a solution, take time to understand not just what it does but why it works. Trace the logic, identify the design decisions implicit in the implementation, and consider alternative approaches. This active engagement converts passive acceptance into genuine learning. You build mental models of patterns and techniques even when you did not write the code yourself. Over time, this accumulated understanding supports better evaluation and more effective guidance of future AI assistance. Understanding Systems at Multiple Levels Deep technical understanding does not mean memorizing API signatures or syntax rules. Those details AI handles well. It means understanding how systems actually work: how data flows through applications, how memory is managed, how networks introduce latency and failure modes, how concurrent operations create race conditions. These fundamentals remain valuable regardless of how code is generated. Invest in understanding the layers beneath your daily work: operating systems, databases, network protocols, hardware constraints. This foundational knowledge provides the conceptual framework for evaluating solutions that AI proposes. Teaching and Mentoring One of the best ways to maintain understanding is to explain it to others. Teaching forces you to articulate knowledge that might otherwise remain tacit. Mentoring junior engineers, writing documentation, or creating internal training materials all strengthen your own grasp of technical concepts. In an AI-augmented world, the ability to transfer knowledge to humans becomes more valuable, not less. Teams need engineers who can onboard new members, explain architectural decisions, and build shared understanding that transcends individual productivity. Learning to Become a Future-Ready Engineer For engineers looking to prepare for this transition, the path forward involves both acquiring new capabilities and strengthening existing foundations. Start with Current AI Tools The best way to understand AI-assisted development is to practice it daily. Integrate available tools (code completion, chatbots, automated review) into your workflow. Pay attention to where they help and where they fail. Develop intuition for effective prompting through iteration and experimentation. Do not wait for AI to mature before engaging with it. The skills of AI collaboration develop through experience, and engineers who start building that experience now will have significant advantages over those who wait. Invest in Architectural Education System design skills do not develop automatically from writing code. Seek out resources specifically focused on architecture: books on design patterns and system design, case studies of how companies build scalable systems, discussions of architectural trade-offs in different contexts. Practice architectural thinking by analyzing existing systems. Pick apart open-source projects, study how production systems handle scale and failure, and articulate the design decisions you observe. This analytical capacity transfers directly to AI-augmented work. Expand Domain Knowledge Cross-domain fluency requires intentional effort to learn adjacent areas. If you are primarily a backend engineer, spend time understanding frontend frameworks, mobile development constraints, or infrastructure automation. You do not need deep expertise everywhere, but working familiarity with multiple domains increases your effectiveness. AI can accelerate this learning by explaining unfamiliar concepts and generating example code in new languages. Use it as a learning partner to expand your capabilities more quickly than would be possible through documentation alone. Strengthen Communication Skills As implementation becomes less central to the role, the ability to communicate clearly (with humans and with AI) becomes more important. Practice articulating technical concepts precisely, writing clear documentation, and translating between technical and business perspectives. Good prompting is essentially good communication. Engineers who write clearly, think precisely about requirements, and can explain their intentions unambiguously will extract more value from AI assistance than those who cannot. Build Projects End-to-End Nothing builds comprehensive engineering judgment like owning a project from conception to production. Seek opportunities to work across the full stack, handle deployment and operations, and deal with the messy realities of running software in production. Side projects can serve this purpose even when your day job offers limited scope. Building something complete, even something small, exercises muscles that specialized work does not reach. The Opportunity in Transition It would be dishonest to pretend this transition is purely positive for all engineers. Some roles that exist today will change significantly or disappear. Some skills that once guaranteed employment will become commoditized. The adjustment will be uncomfortable for many. But for engineers who approach this moment with clear eyes, there is genuine opportunity. The total demand for software continues to grow faster than the supply of capable engineers. AI extends what engineers can accomplish without eliminating the need for engineering judgment. And the new skills required (architectural thinking, precise specification, quality evaluation) are intellectually rewarding in ways that rote implementation often is not. The engineers who thrive will be those who recognize that their value lies not in the code they type but in the problems they solve and the judgment they exercise. AI changes the tools available but does not change the fundamental challenge of building systems that work, that scale, and that serve real human needs. That challenge has always been what makes engineering worthwhile. The future belongs to those who embrace it with both technical skill and intellectual honesty. Let's Continue the Conversation These are my thoughts based on years of building software and observing how the industry evolves. But the future is not written yet, and I am curious what you think. Are you already adapting your workflow around AI tools? What skills do you find most valuable as the role changes? Where do you see the biggest challenges? Feel free to reach out through my contact page or connect with me on LinkedIn. If you found this perspective useful, consider following along for more thoughts on software engineering, AI, and the craft of building things that last. --- # Code Is for Humans, Not Machines: Why AI Will Not Make Syntax Obsolete URL: https://www.ivanturkovic.com/2026/01/23/code-is-for-humans-not-machines-why-ai-wont-make-syntax-obsolete/ Published: 2026-01-23 Topics: AI, Software Engineering With AI, "everybody is a programmer." You do not need to learn syntax anymore. Just describe what you want, and the machine will write the code for you. If you have spent any meaningful time in this profession, you are probably laughing right now. Or at least shaking your head. This narrative has become extraordinarily popular. It appears in breathless LinkedIn posts, product marketing copy, and the excited proclamations of people who have just discovered that ChatGPT can generate a working Python script. And I understand the appeal. The idea that programming could become as simple as having a conversation is genuinely compelling. It democratizes access. It removes barriers. It sounds like progress. But it fundamentally misunderstands what programming actually is, why code exists, and what happens after the initial script is generated. Let me explain why experienced engineers are skeptical, and why understanding syntax and language structure remains not just useful but essential. The Persistent Fantasy of Eliminating Programmers Before we examine the current AI moment, it is worth recognizing that we have been here before. Many times. In the 1980s, CASE tools were supposed to make programming obsolete. Business analysts would draw diagrams, and working systems would emerge. In the 1990s, visual programming environments and RAD tools promised the same. The 2000s brought model-driven development. The 2010s gave us low-code and no-code platforms with identical promises. Each generation of tools genuinely improved productivity for certain tasks. And each one failed to eliminate the need for people who deeply understand how software works. The pattern is consistent enough that it should prompt some skepticism when the same claims appear with new technology. AI coding assistants are genuinely more capable than previous generations of automation tools. I use them daily, and they meaningfully accelerate certain types of work. But the fundamental dynamics have not changed as much as the marketing suggests. The Core Misconception: Code as Machine Instructions Here is the central error in the "no more syntax" argument: it assumes that code exists primarily to instruct computers. It does not. Code is a communication medium between humans. The fact that computers can execute it is almost a secondary consideration. If code were purely for machines, we would all write in assembly language or binary. We do not, because humans need to read, understand, modify, debug, and collaborate on code. The entire history of programming language evolution has been about making code more readable and maintainable by humans, not more efficient for machines. Consider why we have: Variable names instead of memory addresses Functions and classes to organize logic Comments and documentation Coding conventions and style guides Code review processes None of these exist for the computer's benefit. They exist because other programmers, including your future self, need to understand what the code does and why. When someone says "you do not need to understand syntax anymore," they are essentially saying "you do not need to read." That should immediately raise questions. The Debugging Reality Here is a scenario that every working programmer encounters regularly: Your AI assistant generates 200 lines of code that looks correct. It handles the happy path beautifully. You deploy it, and three days later, a customer reports strange behavior. The logs show an edge case you did not anticipate. You need to figure out what is happening. Now what? If you cannot read the code, you cannot debug it. If you do not understand the language's semantics, you cannot reason about why a particular sequence of operations produces unexpected results. If you have no mental model of how the runtime works, you cannot interpret error messages or stack traces. You could ask the AI to fix it, certainly. But to do that effectively, you need to describe the problem accurately. And describing a software problem accurately requires understanding what the code is supposed to do versus what it actually does. Which requires reading and comprehending the code. The people I know who use AI assistants most effectively are invariably strong programmers. They leverage AI to accelerate work they already understand. They can evaluate whether generated code is correct, identify subtle bugs, and recognize when the AI has made poor architectural decisions. They are not replaced by the tool; they are amplified by it. What Syntax Actually Represents Syntax is not arbitrary ceremony. It is compressed knowledge about how programs are structured and how they execute. When you learn that a for loop has a particular structure, you are not just memorizing punctuation. You are learning about iteration, scope, initialization, and termination conditions. When you understand why some languages distinguish between equality and assignment operators, you are learning about expression evaluation and side effects. When you grasp how function parameters work, you are learning about calling conventions, stack frames, and data flow. The syntax is the surface representation of these deeper concepts. Knowing the syntax means having instant access to these mental models when reading code. Not knowing it means being unable to engage with code at anything beyond a superficial level. This is why experienced programmers can learn new languages quickly. The syntax is different, but the underlying concepts transfer. They are not memorizing arbitrary rules; they are recognizing patterns they have seen many times before in different disguises. The Limits of Natural Language Specification Advocates for prompt-based programming argue that we can specify what we want in natural language, avoiding the precision requirements of code syntax. This sounds reasonable until you try it for anything non-trivial. Natural language is inherently ambiguous. That ambiguity is a feature for human communication, where context and shared understanding fill in gaps. It is a liability for programming, where precision determines correctness. Consider: "Calculate the average of these numbers." Simple enough. But: Should it be mean, median, or mode? What happens if the list is empty? What precision should the result have? Should null values be included or excluded? What if the numbers overflow during summation? A working program must answer all these questions. Either you specify them in your prompt (at which point you are writing pseudo-code with extra steps), or you accept whatever the AI decides (at which point you have bugs you do not know about yet). Professional software engineering is largely about handling these edge cases correctly. The happy path is usually straightforward. The complexity lives in error handling, boundary conditions, concurrency, security, and performance. These concerns do not disappear because you are specifying requirements in English instead of Python. Reading Code You Did Not Write Even if AI could generate perfect code from natural language specifications, professional programming would still require reading skills. Most professional development involves working with existing codebases. You join a team, and there are hundreds of thousands of lines of code written by people who are no longer available to explain their decisions. You integrate with third-party libraries that have their own conventions and patterns. You debug production issues in systems you have never seen before. The ability to read unfamiliar code, build a mental model of how it works, and reason about its behavior is fundamental to professional software work. AI assistants can help explain code, but using their explanations effectively still requires basic literacy in the language being explained. Think of it this way: automatic translation services are remarkably good now. But that does not mean you should stop learning foreign languages if you plan to live in another country. Reading a menu translation is one thing. Understanding cultural nuances, catching errors, and communicating effectively requires actual language competence. What AI Coding Assistants Actually Change None of this means AI assistants are useless. They are genuinely valuable tools that change how we work. But the nature of that change is different from "nobody needs to know programming anymore." AI assistants excel at boilerplate generation, where the pattern is well-established and the context is clear. They are good at syntax lookup, remembering the exact method signature you need faster than searching documentation. They help with code transformation, converting between formats or refactoring when the requirements are precise. They are useful for exploration, quickly generating examples to learn from. What they do not do well is understand your specific system's constraints, business requirements, or architectural decisions. They cannot evaluate whether generated code fits your particular context. They struggle with novel problems that do not match training data patterns. They produce plausible-looking code that is subtly wrong in ways that require expertise to detect. The correct mental model is not "AI replaces programmers" but "AI accelerates programmers." The baseline competence required to use these tools effectively has not decreased. If anything, the ability to evaluate AI output requires broader knowledge than pure coding did before. The Skills That Actually Matter More Now Ironically, the rise of AI coding assistants makes certain traditional skills more important, not less. Code review skills matter more because AI generates more code faster, and someone needs to evaluate it. Debugging skills matter more because you are working with code you did not write, often with subtle issues. Architectural thinking matters more because AI operates at the tactical level and humans must provide strategic direction. Understanding fundamentals matters more because AI mistakes often involve deep concepts that surface-level knowledge cannot detect. The people who will struggle are those who could barely code before and now expect AI to handle everything. They will produce broken software faster than they did before, which is not an improvement. The people who will thrive are strong engineers who use AI to multiply their capabilities while maintaining the judgment to guide it effectively. They write prompts that include necessary context because they understand what context matters. They recognize when generated code is wrong because they know what right looks like. They debug AI mistakes efficiently because they can read and reason about code. A More Honest Framing Here is what I think we can honestly say about AI and programming: AI assistants are powerful tools that meaningfully increase programmer productivity for certain tasks. They lower the barrier to writing simple programs, which is genuinely good for accessibility. They change the nature of some programming work, shifting effort from writing to reviewing and integrating. But they do not eliminate the need to understand how software works. They do not remove the requirement to read and comprehend code. They do not make syntax knowledge obsolete. They do not replace the judgment that comes from years of building and maintaining real systems. If you want to use AI coding tools effectively, learn to program first. Understand the languages you work with. Build mental models of how systems behave. Develop the ability to read code critically and debug systematically. These skills are not made obsolete by AI; they are prerequisites for using AI well. The "everybody is a programmer now" narrative makes for exciting headlines, but it does not match reality. Code remains a communication medium between humans, and reading remains a prerequisite for effective writing. That has not changed, regardless of how impressive the latest demos are. Final Thoughts I use AI coding assistants daily. They make me more productive. I recommend them to colleagues. And I remain convinced that deep knowledge of programming languages, syntax, and fundamentals is essential for anyone who wants to build professional software. These are not contradictory positions. Tools extend capabilities; they do not replace understanding. A power saw makes a carpenter faster, but it does not teach them which joints to use or how to read blueprints. If you are starting your programming journey, learn the fundamentals. Understand syntax. Build things from scratch. Struggle with debugging. Develop the reading skills that will serve you throughout your career. Then add AI assistants to your toolkit, and you will use them far more effectively than someone who skipped those foundations. Code is for humans to read. That remains true regardless of who, or what, writes it. What are your thoughts on AI and programming fundamentals? I would love to hear from engineers who use these tools daily. Connect with me on LinkedIn or follow me on Threads for more discussions on the practical realities of AI in software development. If you found this perspective useful, consider exploring more articles on ivanturkovic.com where I write about software engineering, AI, and building technology with intention. --- # The Eternal Promise: A History of Attempts to Eliminate Programmers URL: https://www.ivanturkovic.com/2026/01/22/history-software-simplification-cobol-ai-hype/ Published: 2026-01-22 Topics: AI, Career & Leadership, Software Engineering When I look back at the history of software, one pattern emerges with remarkable consistency: the promise to simplify software creation, to make it cheaper, and ultimately to eliminate the need for programmers altogether. This is not a new idea. It has been the driving ambition of our industry since the 1960s. And while each generation believes they are witnessing something unprecedented, they are actually participating in a cycle that has repeated itself for over six decades. Today, as large language models generate code and AI assistants pair-program with developers, we hear familiar refrains: programming as we know it is ending, software development will be democratized, and soon anyone will be able to build complex systems without writing a single line of code. These claims deserve scrutiny, not because they are entirely wrong, but because they echo promises made in 1959, in 1973, in 1985, and in 2015. Understanding this history is essential for anyone trying to make sense of where we actually are and where we might be going. The Original Sin: COBOL and the Business User Dream The story begins in the late 1950s, when programming was genuinely arcane. Programmers wrote in assembly language or machine code, manipulating registers and memory addresses directly. The work required deep technical knowledge and was painfully slow. Businesses needed software, but the people who understood business problems rarely understood computers, and the people who understood computers rarely understood business problems. Enter Grace Hopper and the CODASYL committee. In 1959, they created COBOL, the Common Business-Oriented Language. The explicit goal was revolutionary: create a programming language so close to English that business managers could read it, understand it, and eventually write it themselves. The syntax was deliberately verbose. Instead of cryptic symbols, COBOL used words like MOVE, ADD, MULTIPLY, and PERFORM. A program read almost like a bureaucratic memo. The marketing was clear: COBOL would eliminate the bottleneck of specialized programmers. Business analysts would write their own programs. The priesthood of technical experts would be disbanded. Software creation would be democratized. It did not work out that way. COBOL succeeded spectacularly as a programming language. It became the backbone of banking, insurance, and government systems worldwide. Billions of lines of COBOL code still run today, processing trillions of dollars in transactions. But COBOL did not eliminate programmers. Instead, it created a new profession of COBOL programmers. The language was readable, but writing correct, efficient, maintainable COBOL still required specialized skills, deep understanding of the underlying systems, and years of experience. The irony is profound. A language created to eliminate the need for programmers became one of the most enduring job creators in the history of computing. Today, COBOL programmers are in high demand precisely because so few people learned the language, and the systems they maintain are too critical to replace. The First AI Winter: Expert Systems and the 1970s Hype Cycle Most people discussing AI today seem unaware that we have been here before. The 1960s and early 1970s saw an explosion of optimism about artificial intelligence that makes current predictions seem modest by comparison. In 1965, Herbert Simon, one of the founders of AI research, predicted that within twenty years, machines would be capable of doing any work a man can do. In 1967, Marvin Minsky, another AI pioneer, predicted that within a generation, the problem of creating artificial intelligence would be substantially solved. The funding poured in. Government agencies, particularly DARPA in the United States, invested heavily in AI research. The implications for software development were considered revolutionary. If machines could think, surely they could program themselves. Researchers worked on automatic programming, systems that would translate natural language specifications into working code. Expert systems promised to capture human expertise in rule-based engines that could make decisions and solve problems without human intervention. The vision was compelling. Describe what you want in plain English, and the computer would figure out how to do it. Programming would become specification, and specification would become conversation. By the mid-1970s, reality had intervened. The Lighthill Report of 1973 devastated AI funding in the United Kingdom by systematically exposing the gap between AI promises and AI achievements. The report noted that early successes in narrow domains had created unrealistic expectations. Systems that worked in toy problems failed in real-world complexity. The combinatorial explosion of possibilities overwhelmed the computational resources of the time. What followed was the first AI winter. Funding collapsed. Research programs were canceled. The phrase artificial intelligence became an embarrassment in some circles. Researchers rebranded their work as expert systems, knowledge engineering, or computational intelligence to distance themselves from the discredited hype. The lesson should have been clear: translating human intent into working software is fundamentally difficult. The gap between natural language specification and correct implementation is not a technical problem to be solved but a conceptual challenge that reflects deep truths about human communication and software complexity. Fourth-Generation Languages: The 1980s Promise By the early 1980s, a new hope emerged: fourth-generation languages, or 4GLs. The reasoning was seductive. Assembly language was first generation. FORTRAN and COBOL were second generation. Structured languages like Pascal and C were third generation. The fourth generation would abstract away even more complexity, allowing non-programmers to create applications through high-level declarations rather than procedural code. Products like FOCUS, NOMAD, Ramis, and later PowerBuilder and Microsoft Access promised to revolutionize software development. Instead of writing code, users would define data structures, specify business rules, and design screens through graphical interfaces. The underlying code would be generated automatically. The marketing materials were remarkably similar to what we hear about no-code platforms today. End users would build their own applications. IT departments would become enablers rather than bottlenecks. Development time would be reduced by factors of ten or more. 4GLs achieved genuine success in certain domains. Report generation, simple database applications, and departmental tools were built faster with these technologies. Microsoft Access, in particular, empowered millions of users to create functional applications without traditional programming. But the fundamental promise once again went unfulfilled. Complex applications still required complex thinking. When business logic became intricate, when performance mattered, when systems needed to integrate with other systems, 4GLs revealed their limitations. Generated code was often inefficient. Customization was difficult. And the most sophisticated users of 4GLs were not business analysts but specialized developers who understood both the tools and the underlying principles. The pattern repeated: tools that promised to eliminate programmers instead created new categories of programming work. CASE Tools and the Cathedral Builders The late 1980s and early 1990s saw the rise of Computer-Aided Software Engineering, or CASE tools. The vision was comprehensive: model your entire system using diagrams and specifications, and the tools would generate complete, working applications. Companies invested millions in CASE tool suites. Conferences were held. Methodologies were developed. The promise was that software engineering would finally become real engineering, with predictable outcomes, repeatable processes, and automatic generation of code from specifications. I remember this era vividly. Consultants would arrive with massive data dictionaries and entity-relationship diagrams. Projects would spend months in the modeling phase, producing elaborate documentation that was supposed to capture every requirement and design decision. The code generation phase would then transform these models into working systems. The reality was disappointing. Generated code was often bloated and inefficient. The models were difficult to maintain as requirements changed. And the fundamental problem remained: getting the specification right was at least as hard as writing the code directly. The diagrams and models were just another programming language, one that happened to use boxes and arrows instead of text. By the mid-1990s, CASE tools had largely faded from prominence. Some concepts survived in object-oriented analysis and design tools like Rational Rose, but the grand vision of automatic code generation from specifications had proven illusory. The Second AI Wave: Expert Systems Redux The 1980s also saw a second wave of AI enthusiasm, focused on expert systems. Companies like Inference Corporation, Intellicorp, and Teknowledge promised to capture human expertise in rule-based systems that could make decisions as well as human experts. The applications were compelling: medical diagnosis, financial analysis, equipment configuration, and yes, automatic programming. Systems like R1/XCON at Digital Equipment Corporation achieved genuine success, saving the company millions by automatically configuring computer systems. The Japanese Fifth Generation Computer Project, launched in 1982 with massive government investment, aimed to create computers that could reason using logic programming. The explicit goal was to leapfrog Western technology and create machines capable of natural language understanding, automatic programming, and artificial intelligence. By 1990, the Fifth Generation Project had largely failed to achieve its goals. Expert systems proved brittle, failing in unexpected ways when confronted with situations outside their narrow expertise. Maintaining the rule bases became increasingly difficult as systems grew. And the fundamental challenge of knowledge acquisition, getting human experts to articulate their expertise in a form machines could use, proved far harder than anticipated. The second AI winter arrived in the early 1990s. Again, funding collapsed. Again, researchers rebranded their work. Again, the fundamental promise of machines that could program themselves receded into the future. The Internet Era: Web Development and the Democratization Dream The mid-1990s brought the World Wide Web, and with it a new hope for simplified software creation. HTML was supposed to be so simple that anyone could build web pages. And indeed, millions learned to create basic websites with nothing more than a text editor and some determination. But the web quickly became more complex. JavaScript added interactivity. CSS added styling. Server-side programming became essential. Databases backed the content. Security became critical. Performance became competitive. The response was predictable: new tools promising to simplify web development. Dreamweaver, FrontPage, and later WordPress and Wix promised that anyone could build professional websites without coding. Content management systems abstracted away the complexity. These tools succeeded in making basic web presence accessible to non-developers. But professional web development became more complex, not simpler. The rise of single-page applications, mobile-first design, progressive web apps, and the modern JavaScript ecosystem created new layers of complexity that required new categories of specialists. The pattern continued: tools that simplified basic tasks enabled more ambitious projects, which required more sophisticated skills, which created demand for more specialized developers. Model-Driven Architecture and the UML Dream The early 2000s saw another attempt at automatic code generation through Model-Driven Architecture, or MDA. The Object Management Group proposed that platform-independent models could be transformed into platform-specific implementations through automated tooling. The Unified Modeling Language (UML) became the standard notation. Tools like Rational Rose, Together, and Enterprise Architect promised to generate complete applications from UML diagrams. The vision was familiar: model your system at a high level of abstraction, and let the tools handle the implementation details. MDA achieved some success in specialized domains, particularly where the mapping from models to code was well-understood and the generated code did not need extensive customization. But for most applications, the approach proved cumbersome. Maintaining synchronization between models and code was difficult. The models themselves became complex, requiring specialized skills to create and maintain. By the 2010s, MDA had faded from mainstream practice, though its concepts influenced modern code generation and domain-specific languages. The No-Code and Low-Code Renaissance Starting around 2015, a new generation of no-code and low-code platforms emerged. Names like Bubble, Webflow, Airtable, Zapier, and Microsoft Power Platform promised to enable citizen developers to build applications through visual interfaces and drag-and-drop components. The marketing echoed the promises of every previous generation. Business users would build their own applications. IT backlogs would disappear. Digital transformation would accelerate. Programmers would become obsolete, or at least far less necessary. No-code tools have achieved genuine success in their domains. Simple applications, workflow automation, basic websites, and internal tools can be built faster with these platforms than with traditional development. For certain categories of problems, they represent a genuine advancement. But the pattern holds. Complex applications still require complex thinking. When requirements exceed the platforms' capabilities, users hit walls. Integration with existing systems is often difficult. Performance at scale is challenging. And the most sophisticated users of no-code platforms are often former developers who understand the underlying principles. More importantly, no-code has not reduced the demand for traditional developers. If anything, the explosion of digital applications has increased demand. The market for software developers continues to grow, even as no-code platforms proliferate. Large Language Models: The Current Wave Which brings us to the present moment. Large language models like GPT-4, Claude, and Gemini can generate functional code from natural language descriptions. GitHub Copilot and similar tools assist developers in real-time, suggesting completions and generating boilerplate. The improvements are genuine and impressive. The predictions are predictably bold. Programming jobs will be eliminated. Software development will be democratized. Anyone will be able to build complex systems by describing what they want in plain English. We have heard this before. In 1959, with COBOL. In 1973, with expert systems. In 1985, with 4GLs. In 1995, with CASE tools. In 2015, with no-code. This does not mean the current wave is identical to previous waves. Large language models represent a genuine capability breakthrough. They can perform tasks that previous technologies could not. The code they generate is often correct and useful. The productivity improvements for skilled developers are real. But the fundamental challenge remains unchanged: translating human intent into correct, efficient, maintainable, secure software is hard. Not because the tools are inadequate, but because the problem is inherently complex. Why the Promise Always Falls Short Understanding why each wave of simplification tools falls short of its promises requires examining the nature of software development itself. Software is not just code. It is a precise specification of behavior under all possible conditions. When you say you want a simple e-commerce application, you are implicitly specifying thousands of decisions about user authentication, payment processing, inventory management, shipping calculations, tax handling, error recovery, accessibility, performance under load, and security against attacks. Most of these decisions are not obvious from a high-level description. The hard part of software development has never been typing code. It has always been figuring out exactly what the software should do, and ensuring it actually does that under all circumstances. This is why specification languages and automatic code generation repeatedly fail to eliminate programmers: they simply move the complexity from code to specification, and specification is at least as difficult. Furthermore, software exists in an ecosystem. It must integrate with other software, adapt to changing requirements, run on evolving platforms, and serve users whose needs constantly shift. Maintaining and evolving software over time requires understanding that transcends any specification language or generation tool. Finally, software reflects human decisions about tradeoffs. Performance versus maintainability. Security versus usability. Flexibility versus simplicity. These decisions require judgment that cannot be automated because they depend on context, priorities, and values that differ across situations. What Actually Changes Each wave of simplification tools does change something real, just not what the marketers promise. COBOL did not eliminate programmers, but it did make business programming more accessible and created an enormous industry. 4GLs did not replace traditional languages, but they did enable rapid development of certain categories of applications. No-code platforms do not make developers obsolete, but they do empower non-developers to create useful tools. The pattern is consistent: new tools lower the barrier to entry for simple tasks, which increases the total amount of software being created, which creates demand for more sophisticated software, which creates demand for more sophisticated developers. This is not a failure. It is how technology evolves. Each abstraction layer enables new capabilities. Each simplification enables new ambitions. The overall effect is positive, even if the specific predictions prove wrong. Learning from History What should we take from this history as we navigate the current wave of AI-powered development tools? First, skepticism about extreme predictions is warranted. Claims that programming will be eliminated within a few years echo claims made in every previous decade. The pattern suggests that such predictions consistently overestimate the pace of change and underestimate the complexity of the challenge. Second, genuine improvements are real. Large language models do provide productivity gains for developers. They do make certain tasks easier. Dismissing these improvements because previous hype cycles failed would be as mistaken as accepting the most extreme predictions uncritically. Third, the nature of programming work will continue to evolve. Just as COBOL programmers do different work than assembly language programmers, and web developers do different work than COBOL programmers, future developers will do different work than current developers. The work will change, but it will not disappear. Fourth, human skills remain essential. Understanding requirements, making design decisions, debugging unexpected behaviors, maintaining systems over time, and communicating with stakeholders are skills that every wave of automation has failed to replace. These skills may become more important, not less, as tools handle more of the routine work. Fifth, the developers who thrive will be those who learn to use new tools effectively while maintaining deep understanding of underlying principles. Tools come and go, but fundamental knowledge about algorithms, data structures, system design, and software architecture remains valuable across generations. The Enduring Value of Understanding Perhaps the most important lesson from six decades of promised simplification is this: there is no substitute for understanding. Every successful use of COBOL required understanding of business processes and data management. Every successful use of 4GLs required understanding of databases and application architecture. Every successful use of no-code platforms requires understanding of workflow design and system integration. And every successful use of AI code generation requires understanding of software principles, code quality, and system design. The tools change. The languages change. The platforms change. But the need for people who deeply understand what they are building, and why, remains constant. This is not a bug in the system. It reflects something fundamental about the nature of software and the nature of problem-solving. Software is crystallized thought. Creating good software requires good thinking. No tool can substitute for that. Looking Forward with Clear Eyes As we look to the future, the history of software simplification offers a valuable perspective. We should expect AI tools to become more capable. We should expect new promises of revolutionary change. We should expect some of those promises to be fulfilled in unexpected ways, and others to fall short of the hype. Most importantly, we should expect the fundamental challenge to remain: building software that does what people actually need, reliably, efficiently, and securely, in a world where requirements constantly change and systems must work together in complex ways. That challenge has never been primarily about the difficulty of typing code. It has always been about the difficulty of thinking clearly, communicating precisely, and making good decisions under uncertainty. Those are human skills, and they will remain valuable for as long as software matters. The programmers of the future may write less code directly. They may work at higher levels of abstraction. They may use tools we cannot currently imagine. But they will still be doing the essential work of translating human intent into software that works. And that work will continue to require skill, judgment, and understanding that no tool has yet replaced. History suggests that reports of programming's death have been greatly exaggerated, repeatedly, for over sixty years. There is no reason to believe this time is fundamentally different. There is every reason to believe that those who invest in deep understanding will continue to be valuable, regardless of what tools emerge. Final Thoughts If you found this perspective valuable, I encourage you to follow my work. I write regularly about the intersection of software engineering, technology history, and the evolving role of AI in our industry. My goal is always to cut through the hype and offer calm, analytical perspectives grounded in real experience. You can find me on Twitter (X) or LinkedIn where I share shorter insights and engage with the broader technology community. For longer explorations like this one, this blog remains my primary home. I would love to hear your thoughts on this topic. Have you lived through any of these hype cycles? Do you see patterns in your own experience that echo this history? What do you think makes the current AI wave different, or similar, to what came before? Leave a comment below, reach out via the contact page, or connect with me on social media. The best insights often come from the community, from people who are building, leading, and thinking about these questions every day. Until next time, keep building wisely. --- # AI-Powered Fixed-Cost Development: A New Model for Agencies URL: https://www.ivanturkovic.com/2026/01/21/ai-fixed-cost-development-agencies-consultants/ Published: 2026-01-21 Topics: AI, Software Engineering, Startups & Business Software development has always carried an uncomfortable truth: nobody really knows how long it will take. Clients want certainty. They want a number, a deadline, a budget they can plan around. Agencies and independent consultants want to deliver that certainty, but they have learned through painful experience that software estimation is more art than science. The gap between what clients need and what developers can promise has created decades of tension, scope creep, budget overruns, and strained relationships. AI is changing this equation in ways that most people have not yet fully understood. This is not about replacing developers or automating creativity away. It is about controlling the most unpredictable variable in software projects: the time between understanding what needs to be built and actually building it correctly. The Real Problem with Software Estimation Before we talk about solutions, we need to be honest about the problem. Software estimation fails for predictable reasons. The initial requirements are never complete. Edge cases emerge during implementation. Technical debt in existing systems creates unexpected friction. Developers underestimate complexity because optimism is a survival trait in our industry. Clients change their minds as they see the product take shape, which is actually healthy behavior but devastating to fixed timelines. The traditional response has been time-and-materials billing. You pay for hours worked, and the final cost is whatever it turns out to be. This protects the agency from estimation risk but transfers all uncertainty to the client. It also creates misaligned incentives: the agency profits from longer projects, while the client benefits from shorter ones. Fixed-cost projects flip this dynamic. The agency bears the estimation risk but gains the opportunity to profit from efficiency. The client gets budget certainty but accepts that the scope must be clearly defined upfront. In theory, this alignment should produce better outcomes for everyone. In practice, fixed-cost software projects have been notoriously difficult to execute profitably. The estimation problem does not disappear just because you have committed to a number. It simply becomes your problem instead of the client's problem. Where AI Changes the Calculation AI does not solve the estimation problem by making estimates more accurate. It solves it by compressing the feedback loop between writing code and validating that the code does what it should. Consider what happens when a developer implements a feature. They write the code, run it locally, discover it does not quite work, debug it, try again, realize they misunderstood a requirement, ask for clarification, wait for a response, adjust their approach, write more code, run tests, find edge cases they had not considered, fix those, submit for review, receive feedback, make changes, and eventually reach a state where the feature is complete and correct. This cycle is where projects go off track. Each step introduces the possibility of delay, misunderstanding, or rework. The cumulative effect of hundreds of these cycles across a project is what makes software estimation so unreliable. AI tools, when properly orchestrated, can dramatically compress this loop. Not by writing perfect code on the first attempt, but by accelerating each step of the cycle and catching problems earlier when they are cheaper to fix. The Orchestration Mindset The key word here is orchestration. AI is not a magic wand that you wave at a project and watch it complete itself. It is a powerful tool that requires skill to use effectively. The difference between agencies that will succeed with AI-assisted fixed-cost projects and those that will fail comes down to how well they orchestrate the AI into their workflow. Orchestration means understanding what AI does well and what it does poorly. It means knowing when to let AI generate code and when to step in with human judgment. It means building processes that leverage AI's speed while maintaining the quality standards that clients expect. Let me be specific about what good orchestration looks like in practice. AI-Assisted Code Generation with Human Oversight Modern AI coding assistants can generate substantial amounts of functional code from well-written prompts. This is genuinely useful, but it is also dangerous if you treat the output as finished work. The orchestration approach is to use AI for the first draft and then apply rigorous human review. You are not checking whether the code runs. You are checking whether it handles edge cases correctly, follows your architectural patterns, maintains security best practices, and will be maintainable by the team six months from now. This review process is where experienced developers add the most value. They catch the subtle issues that AI misses: the race condition that only appears under load, the SQL injection vulnerability hiding in a dynamic query, the business logic that technically works but violates an unstated assumption about how the client's domain actually operates. When you structure your workflow this way, AI accelerates the tedious parts of implementation while human expertise ensures the result meets professional standards. The combination is faster than either approach alone. Continuous AI-Assisted Testing Testing is where AI's pattern recognition abilities truly shine. AI can generate comprehensive test cases from specifications, identify gaps in test coverage, and even suggest edge cases that human testers might overlook. More importantly, AI can run these tests continuously and summarize the results in ways that highlight what actually matters. Instead of wading through hundreds of test results, you get a clear picture of which features are stable and which need attention. For fixed-cost projects, this continuous testing visibility is invaluable. You know at any moment how much of the specification has been implemented correctly. You can identify problem areas before they spiral into major rework. You can demonstrate progress to clients with confidence because the tests prove that what you have built actually works. AI Code Review as Quality Gate Human code review remains essential, but AI can serve as a first-pass filter that catches common issues before human reviewers spend time on them. AI code review tools can identify potential bugs, security vulnerabilities, performance issues, and style inconsistencies. They can check that the code follows established patterns and conventions. They can flag areas that seem overly complex or poorly documented. This is not about replacing human judgment. It is about focusing human attention where it matters most. When an AI reviewer has already caught the obvious issues, human reviewers can concentrate on the architectural decisions, the business logic, and the subtle quality factors that require experience to evaluate. The result is faster reviews that are also more thorough. The AI catches the mechanical issues while humans focus on the strategic ones. Documentation and Knowledge Management One of the hidden costs in software projects is the time spent explaining decisions, documenting systems, and transferring knowledge between team members. AI dramatically reduces this friction. AI can generate documentation from code, explain complex systems in plain language, and answer questions about the codebase instantly. This means less time spent in meetings, fewer misunderstandings about how things work, and faster onboarding when new team members join a project. For fixed-cost projects, this efficiency directly translates to margin protection. Every hour saved on documentation and knowledge transfer is an hour that can be spent on actual implementation. The Fixed-Cost Calculation Now let us connect this back to the business model that agencies and consultants actually care about. Fixed-cost pricing works when you can accurately predict how long a project will take and then add a margin that compensates you for the risk of being wrong. Traditional software estimation makes this calculation terrifying because the variance is enormous. A project you estimate at 200 hours might take 150 or 400, and you have limited ability to predict which outcome you will get. AI orchestration reduces this variance. It does not eliminate uncertainty, but it compresses the range of likely outcomes. When AI accelerates the feedback loops in development, problems surface earlier. When problems surface earlier, they are cheaper to fix. When problems are cheaper to fix, projects stay closer to their estimates. When AI assists with testing, you know sooner whether features actually work. When you know sooner, you can course-correct before small issues become large ones. When you course-correct early, you avoid the death spiral of rework that consumes fixed-cost margins. When AI handles documentation and knowledge management, your team spends more time on billable implementation work. When more time goes to implementation, you can complete projects with fewer total hours. When you complete projects efficiently, fixed-cost pricing becomes profitable. Real Examples of AI Orchestration in Practice Let me describe some concrete scenarios where AI orchestration transforms fixed-cost project economics. API Integration Projects: A client needs to integrate with a third-party API. Traditionally, this involves studying documentation, writing integration code, handling authentication, managing error cases, and building retry logic. AI can generate the initial integration code from API documentation, suggest test cases for various failure modes, and identify edge cases in the API behavior. What might take a developer two days of careful work can be reduced to a few hours of AI-assisted development plus human review and refinement. Database Schema Design: A project requires designing a database schema for a complex domain. AI can generate initial schema proposals from domain descriptions, identify potential normalization issues, suggest indexes for common query patterns, and flag areas where the design might create performance problems at scale. The experienced database designer still makes the final decisions, but they start from a much more developed foundation. UI Component Development: A client needs a set of custom UI components. AI can generate component code from design specifications, create comprehensive test suites, and suggest accessibility improvements. The developer reviews for quality and consistency, but the initial implementation is dramatically accelerated. Code Refactoring: A project includes cleaning up legacy code. AI can identify code smells, suggest refactoring strategies, generate tests for existing behavior before changes are made, and verify that refactored code maintains the same functionality. The risky manual work of understanding legacy systems is supported by AI analysis. In each case, the pattern is the same: AI handles the time-consuming initial work, humans provide the judgment and quality assurance, and the combined approach is faster and more reliable than either would be alone. Building the Right Team Structure Effective AI orchestration requires rethinking how you structure your development team. The traditional model has senior developers doing complex work while junior developers handle simpler tasks. AI changes this equation because AI is particularly good at generating the kind of straightforward code that junior developers typically write. The new model has senior developers spending more time on orchestration, review, and architectural decisions. They guide the AI, evaluate its output, and handle the genuinely difficult problems that require experience and judgment. Junior developers learn by reviewing AI output and understanding why certain approaches are better than others. This shift actually accelerates junior developer growth. Instead of spending months on repetitive coding tasks, they engage with a wider variety of code patterns and learn to evaluate quality rather than just produce output. For agencies, this means you can take on more projects with the same team size, or deliver projects faster, or both. The leverage from AI orchestration compounds when your team is structured to use it effectively. Managing Client Expectations Fixed-cost projects with AI assistance require clear communication with clients about how the engagement will work. Clients should understand that AI is a tool in your process, not a replacement for human expertise. The value proposition is not that AI will build their product for free. It is that AI allows your team to work more efficiently, which enables you to offer fixed pricing with confidence. Be transparent about your process. Explain that you use AI for initial code generation, that all AI output goes through human review, and that your testing and quality assurance processes ensure the final product meets professional standards. This transparency builds trust and addresses concerns about AI quality. Set clear expectations about scope. Fixed-cost pricing only works when the scope is well-defined. AI orchestration helps you deliver efficiently within that scope, but it does not give you infinite capacity to absorb scope changes. Establish a clear process for handling changes that fall outside the original agreement. The Competitive Advantage Agencies and consultants who master AI orchestration will have a significant competitive advantage in the coming years. They will be able to offer fixed-cost pricing with confidence while competitors struggle with the estimation problem. They will deliver projects faster without sacrificing quality. They will maintain healthy margins on fixed-price work while others either overcharge to compensate for risk or undercharge and lose money. This advantage compounds over time. As you complete more AI-assisted projects, you develop better prompts, better workflows, and better judgment about when to use AI and when to rely on human expertise. Your team becomes more skilled at orchestration, which makes you even more efficient on future projects. The agencies that thrive will be those that view AI as an amplifier of human capability rather than a replacement for it. They will invest in learning how to orchestrate AI effectively, build processes that leverage AI's strengths, and maintain the human judgment that clients ultimately trust. The Risks and Limitations I would be doing you a disservice if I did not discuss the risks. AI makes mistakes. Sometimes these mistakes are subtle and hard to catch. Relying too heavily on AI without proper review processes will eventually produce problems that cost more to fix than you saved by using AI in the first place. AI can hallucinate solutions that look correct but are fundamentally wrong. This is particularly dangerous in domains where correctness matters: security, financial calculations, legal compliance. Human expertise remains essential for validating that AI output actually does what it claims to do. AI tools are evolving rapidly. The workflows that work today may need adjustment as the tools change. You need to stay current with AI capabilities and be willing to adapt your processes as better approaches emerge. Not all projects are equally suited to AI assistance. Highly novel problems, deeply ambiguous requirements, or work that requires extensive domain knowledge may benefit less from AI acceleration. Know when to rely on traditional approaches. Getting Started If you are an agency or independent consultant considering this approach, here is how to begin. Start with a small, well-defined project where you can experiment with AI orchestration without betting the firm on it. Use this project to learn what works for your team, what tools fit your workflow, and how to structure your review processes. Invest in training. Your team needs to understand how to prompt AI effectively, how to evaluate AI output critically, and how to maintain quality while working faster. This is a new skill set that takes time to develop. Build your processes incrementally. Start with AI-assisted code generation and human review. Add AI testing support. Introduce AI code review. Layer in documentation automation. Each addition should prove its value before you move to the next. Track your metrics. Measure how long projects actually take compared to your estimates. Identify where AI saves time and where it does not. Use this data to refine your estimation process and build confidence in your fixed-cost pricing. Be patient. The transition to AI-orchestrated development takes time. You will make mistakes, learn from them, and gradually develop the expertise that makes this approach profitable. The Future of Agency Work We are at the beginning of a fundamental shift in how software gets built. AI will not replace developers, but it will change what developers do and how agencies operate. The agencies that embrace this shift thoughtfully will find themselves able to offer something that has been nearly impossible in software development: genuine predictability. Fixed costs, reliable timelines, and quality outcomes that clients can actually plan around. This is not about racing to the bottom on price. It is about delivering more value efficiently and being rewarded for that efficiency. The best agencies will use AI to take on more ambitious projects, serve more clients, and build a reputation for reliability that attracts premium work. The path forward requires investment: in tools, in training, in process development, and in the judgment to know when AI helps and when it does not. But for agencies willing to make that investment, the opportunity is substantial. AI gives you the ability to control the most unpredictable part of the software development equation. That control is the foundation on which fixed-cost pricing becomes not just possible, but profitable. What do you think about AI-assisted fixed-cost development? Have you experimented with AI orchestration in your agency or consulting practice? I would love to hear about your experiences and perspectives. If you found this article valuable, consider following me for more insights on software engineering, AI in practice, and building sustainable technology businesses. You can also reach out directly if you want to discuss these ideas further or explore how they might apply to your specific situation. Connect with me on LinkedIn or reach out through the contact page on this site. I always appreciate thoughtful conversations about where technology is heading and how we can build it responsibly. --- # The AI Orchestration Developer: Why Team Leaders Will Define the Next Era URL: https://www.ivanturkovic.com/2026/01/19/ai-orchestration-developer-team-leaders-software-engineering/ Published: 2026-01-19 Topics: AI, Career & Leadership There is a particular kind of developer who has spent years doing something that most of the industry undervalues: building people, not just systems. They review code not to gatekeep, but to teach. They pair with junior developers not because it's efficient, but because they understand that growth compounds. They know that a team's ceiling is determined not by its strongest member, but by how well everyone elevates each other. These developers the ones who build teams are about to become the most sought-after professionals in software engineering. Not because the industry has suddenly decided to value mentorship (though it should), but because the skills required to orchestrate AI agents effectively are almost identical to the skills required to lead a team of human developers. This is not a prediction about some distant future. The tools are here now. AI coding assistants have matured from curiosities into genuine productivity multipliers. The question is no longer whether AI will change software development, but who will be equipped to harness it responsibly. The Parallel That Changes Everything Consider what it takes to be an effective technical lead. You need to break down complex problems into discrete, well-scoped tasks. You need to communicate requirements with enough context that someone else can execute without constant supervision. You need to review work critically, catching not just bugs but architectural drift, maintainability issues, and violations of established patterns. You need to know when to trust and when to verify. You need to recognize when someone is confidently wrong. Now consider what it takes to work effectively with AI agents. You need to decompose problems into prompts that an AI can execute. You need to provide sufficient context without overwhelming the model. You need to review generated code critically, catching not just syntax errors but subtle logical flaws, security vulnerabilities, and hallucinated APIs. You need to know when to accept output and when to regenerate. You need to recognize when an AI is confidently wrong. The parallel is not superficial. It reflects something fundamental about the nature of delegation itself. Whether you are delegating to a junior developer or to an AI agent, you are engaging in the same core activity: transferring intent through an imperfect communication channel and then validating that the result matches your expectations. The developers who have spent years refining this skill who have learned through thousands of code reviews and hundreds of mentoring sessions exactly how to bridge the gap between what they mean and what someone else understands have a profound advantage. They have already done the hard work of learning to think explicitly about implicit knowledge. Why Technical Excellence Alone Is Not Enough There is a common assumption that the best AI orchestrators will be the strongest individual contributors the developers who write the most elegant code, who can hold the largest systems in their heads, who solve the hardest technical problems. This assumption is understandable but wrong. Pure technical excellence optimizes for a different problem. The brilliant solo developer has learned to minimize communication overhead by doing everything themselves. They have internalized so much context that they rarely need to articulate it. Their process is efficient precisely because it happens entirely within one mind. But AI orchestration is fundamentally about externalization. You cannot keep context in your head and expect the AI to somehow absorb it. You must learn to make implicit assumptions explicit, to surface hidden dependencies, to articulate what "good" looks like in terms specific enough to be evaluated. These are skills that solo work atrophies rather than strengthens. The developer who has spent years mentoring juniors has been forced to develop exactly these capabilities. Every time they explained why a particular approach was problematic, they had to find words for intuitions that often resist verbalization. Every time they scoped a task for someone else, they had to think carefully about what context was necessary and what could be omitted. Every time they reviewed code, they had to articulate standards that they might otherwise have applied unconsciously. This is not about being a manager rather than an engineer. Many of the best team-builders I know have no interest in management as a career path. They simply understand that engineering is a collaborative discipline, and they have invested in the skills that make collaboration effective. The Hallucination Detection Advantage One of the most dangerous aspects of AI-generated code is its confident incorrectness. An AI will present hallucinated function calls, invented APIs, and subtly flawed logic with the same syntactic polish as correct code. There are no obvious markers that something is wrong. The code compiles. The variable names make sense. The structure looks reasonable. Detecting these errors requires a specific kind of vigilance one that experienced code reviewers have already developed. When you have reviewed thousands of pull requests, you learn to notice when something feels off even before you can articulate why. You develop an instinct for the kinds of mistakes that look plausible but are actually wrong. More importantly, you learn to question confidence. Junior developers often present uncertain work with appropriate hedging. They say "I think this is right" or "I'm not sure about this part." But as developers gain experience, they tend to present work more confidently sometimes more confidently than the work deserves. Learning to probe beneath that confidence, to ask the questions that reveal hidden uncertainty, is a skill that transfers directly to AI oversight. AI agents never hedge. They present everything with equal confidence. The ability to maintain appropriate skepticism in the face of confident output is exactly what experienced reviewers have spent years developing. There is also a pattern-matching component. The developer who has seen hundreds of juniors make the same categories of mistakes off-by-one errors, null pointer assumptions, race conditions, improper error handling knows exactly where to look for problems. They have a mental catalog of failure modes. AI agents have their own characteristic failure modes, and while these are not identical to human mistakes, the meta-skill of knowing where to look transfers. The Economics of AI-Augmented Teams There is an economic reality that will drive demand for AI orchestration skills. AI agents can dramatically increase the throughput of a skilled developer, but only if that developer can effectively direct and validate the output. The multiplication factor is not automatic it depends entirely on orchestration quality. Consider two scenarios. In the first, a developer uses AI assistance naively, accepting output without sufficient scrutiny, fixing errors reactively, and spending significant time debugging hallucinated code. The productivity gain might be modest or even negative once you account for the time spent fixing AI-introduced bugs. In the second scenario, a developer with strong orchestration skills provides well-scoped prompts, maintains appropriate skepticism, catches errors early through systematic review, and knows when to regenerate versus when to manually correct. The productivity multiplication can be substantial not because the AI is doing all the work, but because the human-AI collaboration is efficient. Organizations will pay a premium for developers who can achieve the second scenario consistently. The difference in economic value between naive AI use and skilled orchestration is large enough to create a new tier of compensation for those who can do it well. This is analogous to how the market has always valued developers who can effectively leverage teams. A senior developer who can multiply their impact through delegation and mentorship has always been worth more than a senior developer who can only multiply their impact through longer hours. The same principle applies to AI leverage. What AI Orchestration Actually Looks Like Let me be concrete about what effective AI orchestration involves in practice, because the abstract discussion can obscure the specifics. The orchestration process begins with decomposition. Complex features cannot be handed to an AI agent as monolithic requests. They must be broken into pieces that are small enough to fit within context windows, specific enough to have clear success criteria, and independent enough that errors in one piece do not cascade. This is the same skill required to break work into tasks for a team, with similar considerations about dependencies, parallelism, and interface contracts. Context management is the next critical skill. AI agents have no persistent memory between sessions and limited context within sessions. You must learn to provide exactly the right amount of context enough that the AI can make informed decisions, not so much that important details get lost in noise. This mirrors the challenge of onboarding a new team member: too little context and they make uninformed mistakes, too much and they cannot process it all. Prompt engineering is often discussed as a technical skill, but it is really a communication skill. The best prompts are clear about intent, explicit about constraints, specific about output format, and honest about what constitutes success. These are the same qualities that make for good technical specifications, good user stories, and good mentoring explanations. Review and validation require both rigor and efficiency. You cannot review AI output the way you read a novel, linearly and passively. You must actively probe, testing assumptions, verifying claims, checking edge cases. But you also cannot review everything with maximum intensity that would eliminate any productivity benefit. You must calibrate scrutiny to risk, focusing attention on the parts most likely to contain errors. Experienced code reviewers have already developed this calibration instinct. Iteration is the final skill. AI output is rarely perfect on the first pass. You must learn to refine through follow-up prompts, providing feedback that guides the AI toward better output. This is essentially the same feedback loop involved in code review: identifying issues, explaining why they are problems, and guiding toward solutions without simply doing the work yourself. A Practical Framework for Becoming an AI Orchestration Developer If you recognize yourself in the description of team-builders, you are already well-positioned. The transition involves applying skills you have already developed to a new context. If you are earlier in your career, or have focused more on individual contribution than team leadership, there is a path forward but it requires intentional investment in capabilities that may not have been priorities before. Phase One: Develop Explicit Communication Skills The foundation of AI orchestration is the ability to externalize knowledge that you normally keep implicit. Start by practicing explanation. When you make a technical decision, force yourself to write down why. Not just what you decided, but the constraints you were balancing, the alternatives you considered, and the reasoning that led to your choice. This is valuable documentation, but more importantly, it trains the habit of explicit reasoning. Seek out opportunities to mentor, even informally. Explaining concepts to less experienced developers forces you to find words for things you understand intuitively. Pay attention to the questions they ask these often reveal assumptions you are making without realizing it. Practice writing technical specifications before you write code. Not because this is always the most efficient process, but because it exercises the decomposition and communication muscles that orchestration requires. A specification that someone else could implement without further clarification is good training for prompts that an AI can execute without hallucinating requirements. Phase Two: Develop Critical Review Skills The ability to catch errors in AI-generated code depends on having strong review instincts. If you are not already doing regular code review, start. Volunteer to review pull requests even when it is not required. Pay attention to the patterns what kinds of errors do you catch most often? What areas of the codebase tend to have the most subtle bugs? Build a personal catalog of failure modes. Every time you find a bug, ask yourself whether there is a general category it belongs to. Over time, this catalog becomes a checklist that you run automatically when reviewing any code, human or AI-generated. Practice skeptical reading. When you encounter confident statements in documentation, commit messages, or code comments, make a habit of questioning whether the confidence is warranted. This skepticism transfers directly to AI output, where confident incorrectness is the norm. Study security vulnerabilities. Many AI hallucinations result in insecure code SQL injection, XSS, improper authentication, information disclosure. Familiarity with common vulnerability patterns makes them easier to spot in generated code. Phase Three: Learn the AI Tools Deeply Once you have strong communication and review foundations, it is time to develop specific AI orchestration experience. But approach this deliberately rather than casually. Casual use of AI tools develops bad habits accepting output uncritically, prompting vaguely, failing to iterate. Start by understanding the limitations. Read about hallucination rates, context window constraints, and the kinds of tasks where current AI models struggle. This knowledge calibrates your expectations and tells you where to focus your scrutiny. Experiment with prompt structure. Try different ways of framing the same request and observe how output quality changes. Learn what level of specificity produces the best results for different kinds of tasks. Document your findings prompt engineering is still more art than science, and your own data is valuable. Practice iterative refinement. When AI output is not quite right, resist the urge to fix it manually. Instead, try to guide the AI to a better solution through follow-up prompts. This develops the feedback skills that are essential for efficient orchestration. Build review workflows. When you receive AI output, develop a systematic process for validation. What do you check first? What tests do you run? What questions do you ask? A repeatable workflow catches errors more reliably than ad-hoc inspection. Phase Four: Integrate Orchestration Into Your Practice The goal is not to use AI assistance occasionally, but to integrate it into your regular workflow in a way that sustainably increases your productivity without compromising quality. This requires honest assessment of where AI adds value and where it creates more work than it saves. Identify your high-leverage use cases. For most developers, these include boilerplate generation, test scaffolding, documentation drafts, code explanation, and exploring unfamiliar APIs. For some tasks, current AI tools are net negative usually tasks that require deep understanding of existing system context or tasks where subtle errors have significant consequences. Develop quality gates. Decide what level of review is required for different kinds of AI output. Boilerplate that you will read carefully anyway might need minimal review. Logic that handles edge cases or security-sensitive operations needs thorough scrutiny. Codify these gates so you apply them consistently. Track your results. Note when AI assistance helped and when it hurt. Over time, this data reveals patterns that improve your calibration. Share your findings with colleagues the community is still developing best practices, and everyone benefits from more data. For Experienced Team Leaders: The Transition Path If you are already an experienced technical leader someone who has spent years building teams, mentoring developers, and reviewing code your transition to AI orchestration mastery is more about translation than fundamental skill development. The main adjustment is mental model. You are used to delegating to agents (human developers) who have persistent memory, who can ask clarifying questions, who improve over time through learning, and who have their own context about the system. AI agents have none of these properties. They start fresh every session. They rarely ask clarifying questions they make assumptions and proceed. They do not learn from your feedback in any persistent way. They have no context except what you provide. This means your delegation style must become more explicit and more front-loaded. With human developers, you can rely on shared context building over time. With AI, you must provide all necessary context in every session. With human developers, you can give vague direction and refine through dialogue. With AI, vague direction produces confidently wrong output. Your review instincts are highly transferable, but the failure modes are different. Human developers tend to make errors of omission missing edge cases, forgetting requirements, incomplete implementations. AI agents tend to make errors of commission inventing plausible but wrong solutions, hallucinating APIs, confidently asserting incorrect facts. Adjust your review attention accordingly. Your feedback skills transfer well, but the iteration loop is different. With human developers, feedback aims to build lasting capability you explain principles so they can apply them in future situations. With AI, feedback aims only at the current task. There is no capability building across sessions. This changes the economics of explanation: with humans, investing in thorough explanation pays dividends over time; with AI, explanation only pays off if it improves current output. Consider how to preserve the knowledge you would normally transfer to junior developers. One of the values of mentorship is that it creates redundancy knowledge that was only in your head becomes knowledge in the team. AI assistance does not create this redundancy. You need other mechanisms to capture and share understanding. The Human Element Remains Essential Throughout this discussion, I want to be clear about something that often gets lost in AI discourse: the human element is not just a temporary necessity while AI tools mature. It is permanently essential. AI agents can generate code, but they cannot understand what the code should do. They can produce outputs that match pattern expectations, but they cannot evaluate whether those outputs serve the actual human needs that motivated the project. They can optimize for metrics that you specify, but they cannot tell you whether those metrics actually matter. The most important decisions in software development are not about code they are about what to build, for whom, and why. These decisions require understanding of human needs, business constraints, ethical implications, and long-term consequences that AI tools cannot provide. The developer who becomes merely a prompt-to-code translator loses most of their value. The developer who uses AI to amplify their human judgment becomes dramatically more valuable. This is why the team-building skills matter. They are fundamentally about understanding people what they need, what motivates them, how to communicate with them, how to help them grow. These skills do not become obsolete when AI enters the picture. They become the differentiator between developers who use AI tools and developers who use AI tools effectively. The Responsibility of the Orchestrator There is an ethical dimension to AI orchestration that deserves explicit attention. When you delegate to AI agents, you remain responsible for the output. "The AI did it" is not a defense for bugs, security vulnerabilities, or systems that fail to serve users well. This responsibility parallels the responsibility of technical leadership. A tech lead who delegates a task to a junior developer and deploys the result without review is responsible for any resulting problems. The junior developer made the error, but the tech lead failed in their oversight duty. The same applies to AI assistance the AI generated the bug, but you failed to catch it. Taking this responsibility seriously means being honest about the limits of your oversight capacity. If you cannot effectively review the volume of code you are generating with AI assistance, you are creating risk. Better to generate less code that you can thoroughly validate than more code that might contain hidden problems. It also means being thoughtful about where you apply AI assistance. Some domains healthcare, finance, safety-critical systems have consequences for errors that warrant extreme caution. The productivity benefits of AI assistance must be weighed against the costs of potential failures. Looking Forward The role I have described the AI orchestration developer will evolve as the tools evolve. Current AI coding assistants have significant limitations that shape how we must work with them. Future tools will have different limitations, requiring different approaches. But the fundamental skills will remain relevant. Explicit communication, critical review, appropriate skepticism, effective delegation, understanding of human needs these are not temporary adaptations to current tool limitations. They are core engineering skills that become more valuable, not less, as AI capabilities increase. What changes is leverage. As AI tools improve, the impact of orchestration quality on productivity increases. A small difference in orchestration skill produces a larger difference in output. This amplification effect rewards investment in these skills. It also raises the stakes for getting it right. Poor AI orchestration at scale can produce significant problems rapidly. The developer who generates thousands of lines of subtly buggy code in a day creates more technical debt than the developer who writes hundreds of lines carefully. Speed without quality is negative productivity. Conclusion The developers who have spent years building teams who have invested in mentorship, who have developed strong review instincts, who have learned to communicate technical ideas clearly are uniquely prepared for the AI era. Not because their skills are transferable to AI orchestration as an afterthought, but because the core challenge of AI orchestration is the same challenge they have been mastering all along: how to delegate effectively through an imperfect communication channel. This is not a call to abandon technical depth in favor of soft skills. The most effective AI orchestrators will be those who combine strong technical foundations with strong delegation skills. You cannot review AI output critically without deep understanding of what correct code looks like. You cannot provide useful context without thorough knowledge of the systems you are building. But it is a call to recognize that the skills often dismissed as "soft" communication, mentorship, leadership have become hard requirements for the next phase of software development. The developer who can only write code will be valuable. The developer who can orchestrate AI to write code, validate it rigorously, and direct it toward genuine human needs will be indispensable. The future belongs to those who can work through others, whether those others are human or artificial. Start building those skills now. --- # Why Ruby Might Be the Most AI-Friendly Language Nobody’s Talking About URL: https://www.ivanturkovic.com/2026/01/17/ruby-token-efficiency-llm-ai-friendly-language/ Published: 2026-01-17 Topics: AI, Ruby on Rails A few weeks ago, Martin Alderson published something that caught my attention: a systematic comparison of how token-efficient different programming languages are when fed to large language models. The findings were fascinating. And if you've been following my writing on Ruby, you won't be surprised to hear that Ruby came out looking very good indeed. But this isn't just about bragging rights for Ruby developers. It points to something bigger: a fundamental shift in how we should think about programming language design in an age where AI is increasingly writing and reading our code. The Constraint Nobody Saw Coming Here's the thing about LLMs that breaks most of our mental models about computing: they don't care about CPU cycles the way traditional programs do. What they care about, desperately, is context window. The context window is the amount of information an LLM can hold in its working memory at once. Think of it as the size of the desk where you're working. No matter how fast you can think, if you can only fit three pages on your desk at a time, that's a hard limit on how much information you can work with simultaneously. For software development agents, this matters enormously. A significant portion of your context window is going to be code: the files you're working with, the changes you're making, the diffs you're reviewing. A more token-efficient language means you can fit more code in the window, which means longer productive sessions and better context for the AI to work with. We've spent decades optimizing languages for human readability and machine performance. Now we need to think about a third dimension: AI comprehension efficiency. The Data Alderson's analysis used the RosettaCode project, which contains implementations of over a thousand programming tasks across nearly a thousand languages. He filtered for tasks that had solutions in all 19 of the most popular mainstream languages, then ran them through the GPT-4 tokenizer. The results showed a 2.6x gap between the most efficient and least efficient languages. That's not a marginal difference. That's the difference between fitting one file in your context window and fitting nearly three. The least token-efficient language in the comparison was C. The most efficient was Clojure. And Ruby? Ruby landed in a comfortable second place among mainstream languages, right behind Clojure. This surprised me initially. Ruby isn't usually celebrated for its brevity. It's celebrated for its expressiveness, its elegance, its joy. But when you think about it more deeply, those qualities and token efficiency turn out to be closely related. Why Dynamic Languages Win One of the clearest patterns in the data: dynamic languages consistently outperformed static languages in token efficiency. This makes intuitive sense when you think about what tokenization actually measures. Every type annotation is tokens. Every explicit declaration is tokens. Every semicolon is a token. Every curly brace is a token. Ruby doesn't have type annotations. It doesn't require semicolons. It uses end instead of braces, which might seem like a wash, but Ruby's approach to blocks and implicit returns means you often write less code overall. Compare equivalent code in Ruby and Java: ruby # Ruby def add(a, b) a + b end java // Java public static int add(int a, int b) { return a + b; } The Java version has explicit return type, explicit parameter types, explicit return statement, access modifier, static keyword, and curly braces. Every one of those elements consumes tokens. But here's where it gets interesting: languages like Haskell and F# were barely less efficient than the top dynamic languages, despite being statically typed. Why? Type inference. When the compiler can figure out types without you writing them, you get the safety benefits of static typing without the token cost. This suggests something important about language design for an AI-assisted future: type inference isn't just a convenience feature. It's a fundamental efficiency gain. The Ruby Philosophy Pays Off I've written before about what makes Ruby unique: its blocks, its metaprogramming, its eigenclasses, its modules as mixins. These aren't just interesting language features. They're compression mechanisms. When you can write: ruby users.select(&:active?).map(&:email) Instead of: ruby users.select { |user| user.active? }.map { |user| user.email } Or worse, the equivalent in a more verbose language, you're not just saving keystrokes. You're saving tokens. And when an LLM is reading your code, those savings compound. Ruby's idioms like attr_accessor, delegate, and Rails' conventions like has_many :posts pack enormous semantic density into minimal token counts. A single line of Ruby can express what might take five or ten lines in other languages. This is why I argued in my Christmas post about Ruby's future that the framework is uniquely positioned for AI-assisted development. LLMs work better with Ruby because Ruby gives them more signal per token. The Surprise Contender: Array Languages One of the more surprising findings in Alderson's analysis came from a reader suggestion. J, an array language that uses ASCII instead of APL's special symbols, absolutely dominated at just 70 tokens average. That's nearly half of Clojure's 109 tokens. APL itself, despite its famous terseness, didn't do nearly as well. The problem? Tokenizers aren't optimized for APL's unique glyphs. Each of those beautiful symbols (⍳, ⍴, ⌽) ends up as multiple tokens. This is a crucial insight: token efficiency isn't just about character count. It's about how well your language's patterns match the tokenizer's vocabulary. Common English words and standard programming constructs tokenize efficiently. Exotic symbols often don't. Ruby benefits from this. Its syntax uses common words: def, end, do, if, class. These are well-represented in any tokenizer trained on programming text. The language's surface syntax is almost conversational, which turns out to be exactly what you want for LLM consumption. What This Means for Working with AI Coding Assistants If you're using Claude Code, Cursor, GitHub Copilot, or any other AI coding tool, the token efficiency of your language choice has practical implications: Longer productive sessions. When your code takes fewer tokens to represent, you can work on larger features before hitting context limits. This means fewer interruptions to summarize context or restart conversations. Better AI comprehension. More code in context means the AI has more information to work with when making suggestions. It can see more of your codebase, understand more relationships, and make more informed recommendations. Lower costs. If you're paying per token (which you often are, even indirectly), more efficient code means lower bills. At scale, a 2x token efficiency difference is a 2x cost difference. Faster iteration. Fewer tokens means faster round trips. When you're in a tight feedback loop with an AI assistant, every millisecond matters. I've noticed this effect in my own work. When I'm using Claude Code on a Rails project, I can hold more context in the conversation than when working with more verbose languages. The AI understands more about what I'm trying to do because it can see more of the relevant code. The Convergence of Developer Experience and AI Experience Here's what I find most interesting about these findings: the languages that are most token-efficient tend also to be the languages developers find most pleasant to write. Ruby, Python, Clojure, Haskell. These aren't random languages. They're languages that have consistently ranked highly in developer satisfaction surveys. They're languages people choose when they have a choice. This isn't coincidence. The qualities that make a language pleasant to write, expressiveness, minimal boilerplate, clear intent, are the same qualities that make it token-efficient. When you remove unnecessary verbosity for human readers, you're simultaneously removing unnecessary tokens for AI readers. In my post about Ruby's unique structures, I argued that Ruby's metaprogramming capabilities let you express complex concepts concisely. The same features that make Ruby a joy to write make it efficient to tokenize. The Counterargument: Compilation Feedback There's an important counterpoint here. Languages like Go, C#, and Java may be more verbose, but they offer something valuable in return: compilation feedback. When an AI generates code in a statically typed language, the compiler can immediately tell it whether the code is valid. No waiting for runtime errors. No subtle bugs lurking in type mismatches. The feedback loop is tight and reliable. This matters for AI-generated code because hallucinations are real. An LLM might confidently generate code that calls methods that don't exist or passes the wrong types. In a static language with a good type system, these errors get caught immediately. In a dynamic language, they might not surface until runtime. The ideal, as Alderson notes, might be languages like Haskell and F# that combine excellent type inference (reducing token cost) with strong static typing (providing compilation feedback). You get the best of both worlds: concise code and reliable error checking. Implications for Language Design If context window really is the new constraint, what does this mean for programming language design going forward? Type inference becomes essential, not optional. Languages that require explicit type annotations everywhere will be at a disadvantage. The future belongs to languages that can infer types without you writing them. Syntactic minimalism matters more than ever. Every unnecessary character is a wasted token. Languages should provide concise ways to express common patterns. Convention over configuration has new meaning. Rails' approach, where sensible defaults eliminate boilerplate, is now an AI efficiency strategy. The less configuration you write, the more context you preserve for meaningful code. Metaprogramming is a feature, not a bug. Ruby's ability to generate code at runtime, which some see as dangerous magic, is actually a compression mechanism. It lets you express complex systems in minimal tokens. Standard library design affects AI productivity. Rich standard libraries with well-named methods tokenize efficiently. When users.select(&:active?) is part of the language's vocabulary, it's more efficiently understood than a custom implementation of the same logic. The Broader Picture This analysis is one data point in a larger trend. We're moving from a world where programming languages were optimized purely for human-machine interaction to one where AI is a third participant in the conversation. Code now needs to be readable by humans, executable by machines, and comprehensible to AI systems with finite context windows. The languages that thread this needle well will thrive. Those that don't will face increasing friction in AI-assisted workflows. Ruby, somewhat accidentally, appears to be well-positioned for this transition. Its design philosophy, which has always prioritized developer happiness and expressiveness, turns out to align nicely with the constraints of LLM-based development tools. This doesn't mean everyone should drop everything and switch to Ruby. Language choice involves many factors: ecosystem, team expertise, problem domain, existing codebase. But it does suggest that Ruby developers shouldn't feel apologetic about their language choice in an AI-first future. If anything, they should feel vindicated. Practical Takeaways If you're already using Ruby: Embrace idiomatic Ruby. Those compact patterns (&:method, symbol-to-proc, implicit returns) aren't just elegant. They're AI-efficient. Use Rails conventions. has_many, validates, scope pack enormous semantic density into minimal tokens. Consider Slim over ERB. As I discussed in my templating comparison, Slim's indentation-based syntax eliminates angle brackets and closing tags. That's fewer tokens. Trust metaprogramming where appropriate. Code that generates code can be token-efficient at the interface level, even if the generated code is verbose. If you're choosing a language for a new project: Consider token efficiency as a factor, alongside all the other factors you already consider. Dynamic languages with strong conventions (Ruby, Python) tend to be efficient. Statically typed languages with type inference (Haskell, F#, Rust) can be nearly as efficient while providing compile-time feedback. Verbose languages with explicit typing (Java, C#, Go) will consume more context window. If you're thinking about language evolution: Watch for languages that prioritize both AI comprehension and human readability. Type inference is increasingly table stakes. Convention-over-configuration patterns may become more prevalent across frameworks. Conclusion Token efficiency in programming languages is a constraint we're just beginning to understand. As AI becomes more central to software development, this metric will matter more, not less. Ruby's position in this ranking isn't surprising when you understand Ruby's philosophy. A language designed to make programmers happy tends also to be a language that expresses ideas concisely. And concise expression is exactly what token-constrained AI systems need. The irony is beautiful: a language often dismissed as "slow" or "unserious" by systems programmers turns out to be highly efficient in the dimension that increasingly matters. Not CPU cycles, but information density. Not execution speed, but comprehension bandwidth. As context windows continue to be the bottleneck they are, and as AI assistants continue to become integral to development workflows, language choice will increasingly factor in this new constraint. Ruby developers, as it happens, are already well-positioned for this future. We optimized for joy, and we got efficiency as a bonus. That's a pretty Ruby way for things to work out. Interested in more on Ruby's place in the AI-assisted development landscape? Check out my posts on Ruby's unique structures, when to use which Ruby building blocks, and my 2026 Rails predictions. --- # How AI Saved $2 Million in a Single Day; And It Wasn’t Vibe Coding URL: https://www.ivanturkovic.com/2026/01/13/ai-saved-2-million-not-vibe-coding/ Published: 2026-01-13 Topics: AI, Career & Leadership I recently came across a story that perfectly encapsulates something I've been thinking about for months. It's about a CPO who was handed an unlimited token budget and told to vibe code three MVPs for Q1 2026. They did something unexpected instead. Rather than firing up Lovable or Bolt to start generating code, they opened Claude Code with the browser extension. Their first prompt wasn't "build me a dashboard." It was "load the top 100 users from our app." The second prompt: "Send each user a personal email asking what they would like to see in the app." That afternoon, instead of debugging AI-generated React components, this CPO was having real conversations with their most valuable customers. Eighty-five responses came back. One hundred fifty feature requests. Three common asks emerged from the noise. A week later, those three small features shipped. Not three MVPs. Three focused improvements that users actually wanted. By month's end, sales were up 10% on $20M ARR. That's $2 million in new revenue. From a single prompt. And not a single new app was built. The Seduction of Vibe Coding I get the appeal. I really do. There's something intoxicating about describing an app in plain English and watching it materialise in front of you. It feels like magic. It feels like the future. And honestly, it is impressive technology. The demos are incredible. Someone types "build me a project management tool with Kanban boards and real-time collaboration" and boom there's a working prototype in minutes. It's hard not to get swept up in the excitement. But here's what the demos don't show: what happens three months later when that prototype hasn't moved the needle on any meaningful business metric. The problem isn't the technology. The problem is what we're optimising for. Shipping Fast vs. Shipping Right The startup world has long celebrated speed. "Move fast and break things." "Ship or die." "If you're not embarrassed by your first version, you launched too late." There's wisdom in these mantras. Analysis paralysis kills more companies than bad code ever will. Getting something out the door and learning from real usage beats theorising in a vacuum. But somewhere along the way, we conflated "shipping fast" with "building fast." Vibe coding optimises for building fast. You can generate a complete application in hours. The code might even be decent. The UI might look polished. You can certainly ship it. But shipping a thing nobody wants, faster, doesn't make you more successful. It just makes you fail quicker. The CPO in this story understood something crucial: the bottleneck wasn't development velocity. It was understanding what to build. AI as a Research Multiplier Here's what strikes me most about this example. The CPO didn't abandon AI, they repositioned it. Instead of using AI to generate code, they used it to: Query their own database for high-value users Craft and send personalised outreach at scale Process and categorise 150 feature requests Identify patterns across qualitative feedback This is AI as a research multiplier, not a development shortcut. Think about how long that process would have taken manually. Pulling user data. Writing individual emails. Sending them one by one. Reading and categorising responses. Finding patterns across dozens of open-ended answers. A week's work compressed into an afternoon. But the work wasn't skipped, it was accelerated. The human judgment stayed in the loop. The decisions remained human. The AI just made the research process feasible at a scale that would have been impractical otherwise. The Laws of the Market Don't Care About Your Tech Stack There's a fundamental truth that gets lost in the excitement around new AI capabilities: market dynamics haven't changed. Product-market fit still matters. Unit economics still matter. Solving real problems for real people still matters. You can generate a complete SaaS platform with AI in an afternoon. You still can't skip the work of understanding whether anyone will pay for it. The companies that win aren't necessarily the ones with the best technology. They're the ones that understand their customers most deeply and serve them most effectively. This CPO understood that. Rather than using their unlimited AI budget to produce more software, they used it to understand their users better. The three features they shipped probably could have been built without AI assistance at all. They were "small asks" not complex technical challenges. The AI didn't write the code that mattered. It enabled the conversations that mattered. Pushing AI Further Up Your Stack The phrase that keeps echoing in my mind is "push AI further up your stack." Most conversations about AI in software development focus on the implementation layer. Code generation. Bug fixing. Test writing. Refactoring. These are valuable applications. I use them myself. But they're all downstream of more fundamental questions: What should we build? For whom? Why will they care? The further upstream you push AI assistance, the higher the leverage. A 10% improvement in coding speed might save you a few hours. Correctly identifying what to build in the first place can save you months and millions. This is where the real opportunity lies. Not in generating more code faster, but in understanding problems more deeply, reaching customers more effectively, and making better decisions about where to invest limited resources. What This Means for Technical Leaders If you're a CTO, technical founder, or engineering leader, this story should prompt some reflection. How much of your AI experimentation is focused on code generation versus upstream activities? Are you using these tools to understand your market better, or just to build faster? Consider the entire product development lifecycle. Customer discovery. User research. Competitive analysis. Feature prioritisation. Technical architecture. Implementation. Testing. Deployment. Monitoring. Support. AI can assist with every stage. But we've collectively focused most of our attention on implementation arguably the stage where traditional developers were already most capable. The stages where most teams struggle aren't technical. They're about understanding, prioritising, and communicating. They're about finding signal in noise. A Different Kind of Artisanal Coding I've written before about artisanal coding the idea that thoughtful, intentional software development beats rapid code generation. This story reinforces that perspective, but extends it. Artisanal development isn't just about how you write code. It's about how you decide what code to write. The CPO in this story practiced artisanal product development. They resisted the temptation of unlimited AI tokens and the allure of rapid prototyping. They chose a slower, more deliberate path: talking to users, understanding needs, building only what was validated. The result wasn't just better software. It was better business outcomes. The Uncomfortable Truth About MVPs Let's be honest about what often happens with MVPs. Someone has an idea. They build it quickly. They ship it. Nobody uses it. They iterate. Still nobody uses it. Eventually they run out of runway or patience. The post-mortem usually focuses on execution: should have marketed better, should have iterated faster, should have pivoted sooner. But the root cause is often earlier: they built the wrong thing in the first place. An MVP is supposed to be a learning tool. Minimum Viable Product emphasis on "viable." It's the smallest thing you can build to test a hypothesis about customer needs. Too often, though, the hypothesis being tested is "can I build this?" rather than "will anyone want this?" Vibe coding makes the first hypothesis trivially easy to answer. You can build almost anything now. The question of whether anyone wants it remains exactly as hard as it ever was. Three Practical Takeaways If this story resonates, here are three ways to apply these insights: First, audit your AI usage. Look at how your team is using AI tools. What percentage is focused on code generation versus research, analysis, and customer understanding? Consider deliberately shifting that balance upstream. Second, invest in customer connection infrastructure. The CPO in this story could query their user database and send personalised emails through AI because they had the systems to support it. Make sure you can easily identify your most valuable users, reach them, and process their feedback at scale. Third, resist the urge to build. When you have a hammer, everything looks like a nail. When you have unlimited AI tokens and tools that can generate complete applications, every problem looks like it needs a new app. Sometimes the right answer is a few targeted improvements to what already exists. The Future of AI-Assisted Development I don't think this is an either/or situation. Vibe coding and AI-assisted user research can coexist. They serve different purposes. But I do think we're going to see a maturation in how teams think about AI assistance. The initial gold rush around code generation will settle. The novelty will fade. What will remain is a more nuanced understanding of where AI creates genuine leverage. And increasingly, I believe that's going to be in the upstream activities; the research, analysis, and decision-making that determine whether what you build matters. The companies that figure this out early will have a significant advantage. Not because they can build faster, but because they'll know what to build. Conclusion $2 million in new revenue from a single afternoon of AI-assisted user research. No new apps. No vibe coding. No prototypes that never found product-market fit. Just a CPO who understood that the most valuable use of AI wasn't generating code, it was enabling conversations that would have been impossible otherwise. The tools don't matter. The approach does. Push AI further up your stack. Talk to your users. Build what they actually want. The market will reward you for it. Ivan Turkovic is a technology consultant and fractional CTO helping startups and scale-ups build the right things, not just build things fast. He writes about software development, AI's impact on programming, and cutting through the noise in technology decision-making. --- # What Makes Ruby Different: Unique Structures vs Python, Java, JavaScript URL: https://www.ivanturkovic.com/2026/01/10/ruby-unique-structures-vs-other-languages/ Published: 2026-01-10 Topics: Ruby on Rails In my previous post on Ruby's building blocks, I covered when to use Struct, Data, Class, and Module. But I glossed over something important: many of these constructs don't exist in other languages - or exist in such diminished forms that they barely count. Ruby isn't just another object-oriented language with different syntax. It has genuinely unique structures that change how you think about code. If you're coming from Python, Java, JavaScript, or C#, these are the things that will make you say "wait, you can do that?" This isn't about Ruby being "better" in some abstract sense. It's about understanding what Ruby offers that others don't - and why those differences matter for writing expressive, maintainable code. Struct: Class Generation as a First-Class Feature Most languages make you write classes by hand. Ruby lets you generate them. Person = Struct.new(:name, :email, :age) That single line creates a complete class with: A constructor accepting three arguments Getter and setter methods for each attribute Equality comparison based on values A sensible to_s representation Hash and array-like access (person[:name], person[0]) What Other Languages Offer Python has namedtuple and @dataclass: from dataclasses import dataclass @dataclass class Person: name: str email: str age: int This is close, but it's a decorator that modifies a class you still have to define. You need type annotations (even if you don't want them). And it was added in Python 3.7 - Struct has been in Ruby since the beginning. JavaScript has nothing built-in. You write classes or use plain objects: class Person { constructor(name, email, age) { this.name = name; this.email = email; this.age = age; } } Java added Records in Java 14: public record Person(String name, String email, int age) {} Clean, but it took until 2020 to arrive, and records are immutable - you can't choose. C# has records too (C# 9.0): public record Person(string Name, string Email, int Age); Same story - late addition, immutable by default. Why Ruby's Approach Is Better Ruby's Struct isn't just syntax sugar. It's a class factory - a method that returns a new class. This is possible because Ruby classes are themselves objects. # Struct.new returns a class PersonClass = Struct.new(:name, :email) PersonClass.class # => Class # You can subclass it class Employee < Struct.new(:name, :department) def formatted "#{name} (#{department})" end end # You can generate classes dynamically def create_model(fields) Struct.new(*fields) do def valid? members.none? { |m| send(m).nil? } end end end User = create_model(:id, :name, :email) This meta-programming capability - generating classes at runtime based on data - is natural in Ruby and awkward or impossible in most other languages. Data: Immutable Value Objects Done Right Ruby 3.2 introduced Data - like Struct but immutable. This might seem minor until you realize how other languages handle immutability. Point = Data.define(:x, :y) origin = Point.new(0, 0) origin.x = 5 # NoMethodError - no setter exists The Problem Data Solves In most languages, making something immutable requires discipline or verbosity. Java (pre-records): public final class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } @Override public boolean equals(Object o) { /* ... */ } @Override public int hashCode() { /* ... */ } } That's 20+ lines for what Ruby does in one. Python: from dataclasses import dataclass @dataclass(frozen=True) class Point: x: int y: int Better, but frozen=True is opt-in and easy to forget. And "frozen" dataclasses can still contain mutable objects - the immutability is shallow. JavaScript: const point = Object.freeze({ x: 0, y: 0 }); Object.freeze is shallow, doesn't prevent reassignment of the variable, and provides no structural guarantees. Why Ruby's Data Is Better Ruby's Data makes immutability the point of the construct, not an option you enable. When you see Data.define, you know immediately that instances won't change. It's semantic clarity through distinct types. # The type itself communicates intent Config = Data.define(:api_key, :timeout, :retries) # Compare to Struct where you have to read the code # to know if anyone mutates it Config = Struct.new(:api_key, :timeout, :retries) And like Struct, Data is a class factory with full meta-programming support: Point = Data.define(:x, :y) do def distance_from_origin Math.sqrt(x**2 + y**2) end def +(other) Point.new(x + other.x, y + other.y) end end Blocks: Code as a Casual Parameter Blocks are Ruby's most distinctive feature. They let you pass code to methods as naturally as passing data. [1, 2, 3].map { |n| n * 2 } File.open("data.txt") do |file| file.each_line { |line| puts line } end 3.times { puts "Hello" } What Other Languages Offer Python has comprehensions and lambdas: [n * 2 for n in [1, 2, 3]] # Comprehension - limited to specific patterns list(map(lambda n: n * 2, [1, 2, 3])) # Lambda - verbose Python lambdas are single expressions only. Multi-line logic requires a named function. JavaScript has arrow functions: [1, 2, 3].map(n => n * 2) Close to Ruby's elegance, but JavaScript doesn't have the yield pattern for implicit blocks. Java has lambdas (since Java 8): Arrays.asList(1, 2, 3).stream().map(n -> n * 2).collect(Collectors.toList()); Functional, but verbose. And you need to explicitly create streams. C# has LINQ and lambdas: new[] {1, 2, 3}.Select(n => n * 2); Quite elegant, actually. C# learned from Ruby. Why Ruby's Blocks Are Better Ruby blocks aren't just anonymous functions - they're integrated into the language's control flow. The yield keyword makes blocks feel like native syntax extensions. def with_timing start = Time.now result = yield # Execute the block puts "Took #{Time.now - start} seconds" result end # Use it like a language construct with_timing do expensive_operation end This pattern is awkward in other languages. Python would need decorators or context managers. JavaScript would need callbacks or explicit function calls. Ruby also distinguishes between blocks, procs, and lambdas - giving you control over how code-as-data behaves: # Block: implicit, tied to method call [1, 2, 3].each { |n| puts n } # Proc: stored code, flexible return behavior my_proc = Proc.new { |n| n * 2 } # Lambda: stored code, strict argument checking my_lambda = ->(n) { n * 2 } The ability to capture a block as a proc (&block) and convert between these forms gives you precise control that other languages lack. Modules: Namespacing and Mixins in One Construct Most languages separate namespacing from code sharing. Ruby combines them in modules. module Searchable def search(query) # Shared search logic end end class Article include Searchable end class Product include Searchable end What Other Languages Offer Python uses files as modules (namespacing) and multiple inheritance or mixins (code sharing): # Namespacing: just use the file/package system from myapp.utils import helper # Code sharing: multiple inheritance or mixins class SearchMixin: def search(self, query): pass class Article(Model, SearchMixin): pass Python's approach works, but conflates namespacing with file structure. And multiple inheritance is famously problematic (diamond problem). Java has packages (namespacing) and interfaces with default methods (since Java 8): public interface Searchable { default void search(String query) { // Default implementation } } public class Article implements Searchable { } Interfaces with defaults are Java playing catch-up with Ruby's mixins - and they're limited because interfaces can't have state. JavaScript has ES6 modules (namespacing) and no built-in mixin pattern: // Namespacing: import/export import { helper } from './utils'; // Code sharing: typically Object.assign or class extends const SearchMixin = { search(query) { /* ... */ } }; Object.assign(Article.prototype, SearchMixin); Awkward and doesn't integrate with the class system. C# has namespaces and no mixins: namespace MyApp.Utils { } // Code sharing requires inheritance or extension methods // No direct equivalent to mixins Why Ruby's Modules Are Better Ruby modules serve triple duty: Namespace: Group related code under a name Mixin: Share behavior across unrelated classes Singleton container: Hold module-level methods module Payments # Namespace for related classes class Processor; end class Refund; end # Module-level utility def self.default_currency "USD" end end module Auditable # Mixin for sharing behavior def audit_trail @audit_trail ||= [] end def record_change(change) audit_trail << { change: change, at: Time.now } end end class Invoice < Payments::Processor include Auditable # Mix in auditing capability end The key insight: include inserts the module into the class's ancestor chain, making method lookup work naturally. This is cleaner than multiple inheritance because modules don't carry their own initialization or identity - they're pure behavior packages. Open Classes: Extending Anything, Anytime Ruby classes are never closed. You can add methods to any class - including built-in ones - at any time. class String def palindrome? self == self.reverse end end "radar".palindrome? # => true "hello".palindrome? # => false What Other Languages Offer Python allows monkey-patching but discourages it: def palindrome(self): return self == self[::-1] str.palindrome = palindrome # Works, but frowned upon It's possible but feels like a hack. Python's culture strongly discourages this. JavaScript allows extending prototypes: String.prototype.palindrome = function() { return this === this.split('').reverse().join(''); }; Possible, but universally considered bad practice in JavaScript. Java and C#: Not possible. Classes are closed after compilation. Why Ruby's Open Classes Are Better Ruby doesn't just permit open classes - it embraces them. The ecosystem is built around this capability. ActiveSupport extends core classes tastefully: # From ActiveSupport 1.day.ago "hello_world".camelize [1, 2, 3].sum Refinements provide scoped modifications (since Ruby 2.0): module StringExtensions refine String do def palindrome? self == reverse end end end class MyClass using StringExtensions # Only active in this scope def check(word) word.palindrome? end end "radar".palindrome? # NoMethodError - refinement not active here This gives you the power of open classes with the safety of scoped changes. method_missing: The Ultimate Flexibility When you call a method that doesn't exist, Ruby doesn't just fail - it gives you a chance to handle it. class Flexible def method_missing(name, *args) puts "You called #{name} with #{args.inspect}" end end obj = Flexible.new obj.anything_at_all(1, 2, 3) # => "You called anything_at_all with [1, 2, 3]" What Other Languages Offer Python has __getattr__: class Flexible: def __getattr__(self, name): def method(*args): print(f"You called {name} with {args}") return method Similar capability, but the syntax is clunkier. JavaScript has Proxy (ES6): const handler = { get(target, prop) { return (...args) => console.log(`Called ${prop} with`, args); } }; const obj = new Proxy({}, handler); obj.anything(1, 2, 3); Powerful but verbose and not commonly used. Java and C#: Not possible without reflection hacks that don't integrate with normal method calls. Why Ruby's method_missing Is Better Ruby's method_missing enables some of the language's most elegant patterns. Dynamic finders in ActiveRecord: User.find_by_email("alice@example.com") User.find_by_email_and_status("alice@example.com", "active") These methods don't exist until you call them - ActiveRecord generates them on the fly. Builder patterns: class XMLBuilder def initialize @xml = "" end def method_missing(tag, content = nil, &block) @xml += "<#{tag}>" if block @xml += instance_eval(&block) else @xml += content.to_s end @xml += "" end end builder = XMLBuilder.new builder.html do head do title "My Page" end body do p "Hello, world!" end end This creates a domain-specific language that reads like the output format itself. Symbols: Identity Without the Overhead Symbols look like strings but behave like identifiers. They're immutable, interned, and compared by identity rather than content. :name.object_id == :name.object_id # => true (same object) "name".object_id == "name".object_id # => false (different objects) What Other Languages Offer Python has no direct equivalent. You use strings everywhere: {"name": "Alice", "email": "alice@example.com"} JavaScript added Symbols in ES6, but they're used differently: const name = Symbol('name'); obj[name] = "Alice"; // Private-ish property JavaScript Symbols are for hiding properties, not for readable keys. Java and C#: Use strings or enums. No equivalent concept. Why Ruby's Symbols Are Better Symbols are perfect for identifiers that aren't data - hash keys, method names, options, states. # Hash keys (overwhelmingly idiomatic) user = { name: "Alice", email: "alice@example.com" } # Options redirect_to root_path, notice: "Welcome back!" # States and types status = :pending case status when :pending then handle_pending when :approved then handle_approved end # Method references users.map(&:name) # Same as users.map { |u| u.name } The distinction between symbols and strings makes intent clearer. When you see a symbol, you know it's an identifier. When you see a string, you know it's data that might be displayed or manipulated. Everything Is an Object (And Everything Is Executable) In Ruby, there are no primitives. Numbers, booleans, even nil - all objects with methods. 42.times { puts "hello" } true.to_s # => "true" nil.nil? # => true But it goes further. Classes are objects. Methods are objects. Code blocks can become objects. # Classes are objects of class Class String.class # => Class String.ancestors # => [String, Comparable, Object, Kernel, BasicObject] # Methods can be extracted as objects method_obj = "hello".method(:upcase) method_obj.call # => "HELLO" # Code becomes objects my_block = -> { puts "I'm a lambda" } my_block.call What Other Languages Offer Python is close - most things are objects: (42).__add__(8) # Works type(42) # But Python has some non-object constructs (None is not quite as object-like, there are statement-vs-expression distinctions). JavaScript has object wrappers for primitives, but the distinction exists: typeof 42 // "number" - primitive typeof new Number(42) // "object" - wrapper Java and C# have primitives vs objects, requiring boxing/unboxing. Why Ruby's Consistency Is Better When everything is an object, you don't have mental mode-switching. The same tools (method calls, introspection, extension) work everywhere. # Extend integers class Integer def factorial (1..self).reduce(1, :*) end end 5.factorial # => 120 # Introspect anything 42.methods.grep(/div/) # => [:div, :divmod, :fdiv] # Pass methods around [1, 2, 3].map(&1.method(:+)) # => [2, 3, 4] This consistency reduces cognitive load. You learn one set of patterns and apply them everywhere. The Eigenclass: Object-Level Customization Every Ruby object can have its own private class called the eigenclass (or singleton class). This lets you add methods to individual objects. alice = "Alice" def alice.shout upcase + "!" end alice.shout # => "ALICE!" "Bob".shout # NoMethodError - only alice has this method What Other Languages Offer Python allows adding methods to instances: alice = "Alice" def shout(self): return self.upper() + "!" import types alice.shout = types.MethodType(shout, alice) # Doesn't work - strings are immutable Python can do this with custom classes but not built-in types. JavaScript can add properties to any object: const alice = { name: "Alice" }; alice.shout = function() { return this.name.toUpperCase() + "!"; }; Works, but there's no formal concept of "this object's private class." Java and C#: Not possible without creating a subclass. Why Ruby's Eigenclass Is Better The eigenclass makes Ruby's object model complete. Class methods are actually methods on the eigenclass of the class object: class User def self.count # This is actually defined on User's eigenclass # ... end end # Equivalent to: class User class << self # Open the eigenclass def count # ... end end end This unifies instance methods and class methods under one model - both are just methods, defined on different classes (the regular class vs. the eigenclass). Understanding the eigenclass explains why include adds instance methods while extend adds class methods - extend is actually including into the eigenclass. Why This Matters: Expressiveness and Joy These features aren't academic curiosities. They enable a style of programming that's hard to achieve elsewhere: Domain-Specific Languages that read like natural language: describe User do it "validates email presence" do expect(User.new(email: nil)).not_to be_valid end end Configuration that's just Ruby: Rails.application.configure do config.cache_classes = true config.eager_load = true end APIs that feel native: class Article < ApplicationRecord belongs_to :author has_many :comments validates :title, presence: true scope :published, -> { where(published: true) } end None of this is magic - it's blocks, method_missing, open classes, and modules working together. The Trade-Offs Ruby's flexibility has costs: Performance: Dynamic method dispatch is slower than static. Ruby trades speed for expressiveness. Predictability: When anything can be modified, you can't always be sure what code will do. Discipline matters. Tooling: Static analysis is harder when methods might not exist until runtime. IDEs have more trouble helping you. But these trade-offs are conscious choices. Ruby optimizes for programmer happiness and productivity, trusting developers to use flexibility responsibly. Conclusion: Different, Not Just Different Syntax Ruby isn't just Python with different syntax or Java without types. Its unique structures - Struct as a class factory, blocks with yield, modules as mixins, open classes, method_missing, symbols, the eigenclass - create a fundamentally different programming experience. These features work together. Blocks enable DSLs. Modules enable clean code sharing. Open classes enable tasteful extensions. method_missing enables dynamic interfaces. The eigenclass unifies the object model. Understanding these Ruby-specific concepts is key to writing idiomatic Ruby. Don't just translate patterns from other languages - embrace what makes Ruby different. That's where the joy is. Coming from another language and finding Ruby's flexibility disorienting? Or have you found Ruby constructs that made you wish other languages had them? I'd love to hear your perspective. --- # Ruby’s Building Blocks: When to Use What (And Why) URL: https://www.ivanturkovic.com/2026/01/08/ruby-struct-data-class-module-when-to-use/ Published: 2026-01-08 Topics: Ruby on Rails Ruby gives us an abundance of organizational tools. Struct, Data, classes, modules as namespaces, modules as mixins, service objects, and the include/extend/module_function trinity. Each is well-documented individually, but there's a gap: when and why to choose one over another. This isn't about rules. Ruby's philosophy encourages pragmatism-take what you need and move forward. But pragmatism doesn't mean arbitrary choices. There's idiomatic intent behind each construct, and understanding that intent makes your code clearer to others and to your future self. Ruby is not C++, and we shouldn't think in C++ limitations. But I also don't subscribe to the view that we should avoid classes whenever possible. Classes are a powerful tool when used appropriately. The goal is fit-choosing the construct that best expresses your intent. Struct: The Lightweight Data Container What It Is Struct is a class factory. It generates a new class with named attributes, an initializer, accessor methods, equality comparison, and a reasonable to_s. All from a single line. Person = Struct.new(:name, :email) alice = Person.new("Alice", "alice@example.com") When to Use Struct 1. Quick data grouping without ceremony When you need to bundle a few related values together and don't want the overhead of defining a full class: # Returning multiple related values from a method Result = Struct.new(:success, :data, :errors) def process_order(order) # ... processing logic ... Result.new(true, processed_order, []) end 2. Replacing hashes for clarity When you find yourself passing hashes with consistent keys, a Struct adds structure and catches typos: # Instead of this: config = { host: "localhost", port: 3000, ssl: false } puts config[:hostt] # Silent nil, typo undetected # Use this: Config = Struct.new(:host, :port, :ssl, keyword_init: true) config = Config.new(host: "localhost", port: 3000, ssl: false) config.hostt # NoMethodError - typo caught! 3. Prototyping before committing to a class Struct is perfect for early-stage code when you're still discovering what shape your data needs: # Start here Coordinate = Struct.new(:lat, :lng) # Later, if needed, graduate to a class with behavior 4. Adding light behavior Struct accepts a block for adding methods: Money = Struct.new(:amount, :currency) do def to_s "#{currency} #{amount}" end def +(other) raise "Currency mismatch" unless currency == other.currency Money.new(amount + other.amount, currency) end end When Struct Stops Being Appropriate You need immutability. Struct instances are mutable by default: point = Point.new(1, 2) point.x = 10 # This works, which might not be what you want You need validation. Struct's initializer accepts anything: Person = Struct.new(:name, :age) Person.new(nil, -5) # No complaints You need complex initialization logic. Custom initialize in a Struct can get awkward. You're building a core domain object. If it's central to your application and will accumulate behavior, start with a class. The Struct Graduation Moment When you find yourself: Adding more than 3-4 methods to a Struct block Needing custom validation Wanting to control mutability Wishing you could use inheritance properly It's time to graduate to a class. There's no shame in this-Struct served its purpose during exploration. Data: The Immutable Value Object (Ruby 3.2+) What It Is Data is Ruby's answer to "I want a Struct, but immutable." Introduced in Ruby 3.2, it creates value objects that cannot be modified after creation. Point = Data.define(:x, :y) origin = Point.new(0, 0) origin.x = 5 # NoMethodError - no setter exists When to Use Data 1. When immutability is semantically correct Some concepts are inherently immutable. A coordinate doesn't change-you create a new one: Point = Data.define(:x, :y) do def translate(dx, dy) Point.new(x + dx, y + dy) # Returns new instance end end 2. Safe sharing across threads or contexts Immutable objects can be shared without fear of unexpected modification: Config = Data.define(:api_key, :timeout, :retries) APP_CONFIG = Config.new(api_key: "secret", timeout: 30, retries: 3) # Safe to use from multiple threads 3. Hash keys and set members Data objects make excellent hash keys because their hash value never changes: Coordinate = Data.define(:lat, :lng) visited = {} visited[Coordinate.new(51.5, -0.1)] = "London" 4. Functional programming patterns When you're transforming data through pipelines rather than mutating it: Order = Data.define(:items, :status) do def with_status(new_status) Order.new(items: items, status: new_status) end end order = Order.new(items: [...], status: :pending) processed = order.with_status(:shipped) # New object, original unchanged Data vs Struct: The Decision AspectStructDataMutabilityMutable by defaultAlways immutableRuby versionAll versions3.2+Use caseTemporary grouping, prototypingValue objects, domain conceptsThread safetyNeed to be carefulSafe by design Choose Data when: The concept is inherently immutable (coordinates, money, dates) You want to enforce immutability at the language level You're on Ruby 3.2+ Choose Struct when: You need mutability You're on older Ruby You're prototyping and don't know the final shape yet Regular Class: The Full-Powered Tool When Classes Are The Right Choice Classes remain the right choice more often than minimalist philosophies might suggest. Here's when: 1. Complex initialization with validation class User attr_reader :email, :name, :created_at def initialize(email:, name:) raise ArgumentError, "Invalid email" unless email.include?("@") raise ArgumentError, "Name required" if name.nil? || name.empty? @email = email.downcase @name = name.strip @created_at = Time.now freeze # Optional: make immutable after initialization end end 2. Rich behavior that grows with the domain When an object will accumulate methods as you understand the domain better: class ShoppingCart def initialize @items = [] @discounts = [] end def add(product, quantity: 1) @items << CartItem.new(product, quantity) self end def apply_discount(discount) @discounts << discount self end def subtotal @items.sum(&:total) end def total @discounts.reduce(subtotal) { |amount, discount| discount.apply(amount) } end def empty? @items.empty? end # ... more methods will come as requirements emerge end 3. When you need inheritance Struct and Data can be subclassed, but it's awkward. Classes make inheritance natural: class Vehicle attr_reader :make, :model def initialize(make:, model:) @make = make @model = model end def description "#{make} #{model}" end end class Car < Vehicle attr_reader :doors def initialize(make:, model:, doors: 4) super(make: make, model: model) @doors = doors end end 4. Encapsulation with controlled access When you need private state and careful public interfaces: class BankAccount def initialize(initial_balance) @balance = initial_balance @transactions = [] end def deposit(amount) raise ArgumentError, "Amount must be positive" unless amount > 0 record_transaction(:deposit, amount) @balance += amount end def balance @balance.dup # Don't expose internal state directly end private def record_transaction(type, amount) @transactions << { type: type, amount: amount, at: Time.now } end end The Class Decision Framework Ask yourself: Will this object have significant behavior? → Class Do I need validation on initialization? → Class Will I use inheritance? → Class Is this a central domain concept? → Class Do I need private methods or encapsulated state? → Class Don't be afraid of classes. They're not "heavyweight"-they're expressive. A well-designed class communicates intent clearly. Single-Purpose Objects (Service Objects) What They Are A single-purpose object encapsulates one operation or use case. It typically has: A single public method (usually call) All dependencies injected A focused responsibility class SendWelcomeEmail def initialize(mailer: DefaultMailer, logger: Rails.logger) @mailer = mailer @logger = logger end def call(user) @logger.info "Sending welcome email to #{user.email}" @mailer.deliver( to: user.email, subject: "Welcome!", template: :welcome ) end end # Usage SendWelcomeEmail.new.call(user) When Service Objects Add Clarity 1. Complex operations with multiple steps When an operation involves coordination: class ProcessOrder def initialize(payment_gateway:, inventory:, notifier:) @payment_gateway = payment_gateway @inventory = inventory @notifier = notifier end def call(order) charge_result = @payment_gateway.charge(order.total, order.payment_method) return Result.failure(charge_result.error) unless charge_result.success? @inventory.reserve(order.items) @notifier.order_confirmed(order) Result.success(order) end end 2. Operations that need testing with different dependencies Service objects make dependency injection natural: # In tests fake_mailer = FakeMailer.new service = SendWelcomeEmail.new(mailer: fake_mailer) service.call(user) expect(fake_mailer.sent_emails.size).to eq(1) 3. Cross-cutting operations that don't belong to any single model class ReconcileInventory def call(warehouse_report, system_records) discrepancies = [] warehouse_report.each do |item| system_count = system_records.count_for(item.sku) if item.count != system_count discrepancies << Discrepancy.new(item.sku, item.count, system_count) end end discrepancies end end When Service Objects Add Unnecessary Abstraction 1. Trivial operations Don't create a service object for simple operations: # Unnecessary abstraction class UpdateUserName def call(user, new_name) user.update(name: new_name) end end # Just do this user.update(name: new_name) 2. When the operation clearly belongs to a model If it's fundamentally about one object, make it a method on that object: # Probably unnecessary class ActivateUser def call(user) user.update(active: true, activated_at: Time.now) end end # Better as a method class User def activate! update(active: true, activated_at: Time.now) end end 3. When you're just wrapping another service Adding indirection without value: # Don't do this class SendEmail def call(to:, subject:, body:) ActionMailer::Base.mail(to: to, subject: subject, body: body).deliver_now end end The Service Object Litmus Test Before creating a service object, ask: Does this operation involve multiple collaborators? Do I need to inject dependencies for testing? Is this operation reusable across different contexts? Would putting this in a model make the model too large or unfocused? If yes to multiple questions, a service object probably helps. If it's just moving code around, skip it. Module as Namespace What It Is A module used purely for organization-grouping related constants, classes, and methods under a name. module Payments class Processor # ... end class Refund # ... end class Receipt # ... end end # Usage processor = Payments::Processor.new When to Use Module Namespacing 1. Grouping related classes When you have a family of classes that work together: module Reports class Generator def initialize(data) @data = data end def as_pdf Formatters::PDF.new(@data).render end def as_csv Formatters::CSV.new(@data).render end end module Formatters class PDF # ... end class CSV # ... end end end 2. Avoiding name collisions When generic names might clash: module MyApp class User # Won't conflict with gems that define User end class Error < StandardError # Won't conflict with other Error classes end end 3. Organizing a gem or library module Stripe class Customer end class Charge end class Error < StandardError end module Webhook class Handler end end end 4. Module-level utility methods Using module_function for stateless utilities: module MathUtils module_function def factorial(n) return 1 if n <= 1 n * factorial(n - 1) end def fibonacci(n) return n if n <= 1 fibonacci(n - 1) + fibonacci(n - 2) end end # Can call directly on module MathUtils.factorial(5) # => 120 Namespace Guidelines Don't over-nest. Three levels deep is usually the maximum before navigation becomes painful. Match directory structure. Payments::Webhook::Handler should live in payments/webhook/handler.rb. Use for libraries, be cautious in apps. Deep namespacing in application code can add friction. Module as Mixin What It Is A module that's included in classes to share behavior. Unlike inheritance, which is "is-a," mixins are "has-capability." module Taggable def add_tag(tag) tags << tag unless tags.include?(tag) end def remove_tag(tag) tags.delete(tag) end def tagged_with?(tag) tags.include?(tag) end private def tags @tags ||= [] end end class Article include Taggable end class Photo include Taggable end When to Use Mixins 1. Sharing behavior across unrelated classes When multiple classes need the same capability but don't share an inheritance relationship: module Slugifiable def slug @slug ||= title.downcase.gsub(/\s+/, '-').gsub(/[^\w-]/, '') end end class BlogPost include Slugifiable attr_accessor :title end class Product include Slugifiable attr_accessor :title end 2. Extracting cross-cutting concerns Concerns that appear across your domain: module Auditable def self.included(base) base.extend(ClassMethods) end module ClassMethods def audit_changes(*fields) @audited_fields = fields end def audited_fields @audited_fields || [] end end def save_with_audit changes = audited_changes result = save AuditLog.record(self, changes) if result && changes.any? result end private def audited_changes # Compare current state with original end end 3. Composition over inheritance When you want to compose capabilities rather than build a hierarchy: module Printable def print puts to_s end end module Comparable def <=>(other) sort_key <=> other.sort_key end end class Person include Printable include Comparable attr_reader :name, :birth_date def sort_key birth_date end def to_s "#{name} (born #{birth_date})" end end When Mixins Are Not The Answer 1. When inheritance makes more sense If there's a clear "is-a" relationship: # Don't do this module AnimalBehavior def eat; end def sleep; end end class Dog include AnimalBehavior end # Do this class Animal def eat; end def sleep; end end class Dog < Animal end 2. When the mixin needs too much from the including class If your mixin assumes many methods exist: # Problematic - assumes too much module Publishable def publish! validate! # Assumes this exists set_published_at! # Assumes this exists notify_subscribers! # Assumes this exists update_status! # Assumes this exists end end This creates hidden coupling. The including class must implement a specific interface that isn't enforced or documented. 3. When state management gets complex Mixins that manage significant state can create confusion: # Gets confusing quickly module StateMachine def initialize @state = :initial @transitions = {} end # ... lots of state management end Consider a composed object instead. Mixin Best Practices Keep mixins focused. One capability per module. Document expectations. If you need methods from the including class, document them. Prefer stateless mixins. Behavior without instance variables is simpler. Use included hook sparingly. It adds magic that can confuse. Include, Extend, and Module Function These three mechanisms serve different purposes. Understanding them prevents confusion. Include: Instance Methods include adds module methods as instance methods of the including class. module Greetable def greet "Hello, I'm #{name}!" end end class Person include Greetable attr_accessor :name end alice = Person.new alice.name = "Alice" alice.greet # => "Hello, I'm Alice!" Person.greet # => NoMethodError Use include when: You want objects (instances) to have the behavior. Extend: Class Methods extend adds module methods as class methods (or singleton methods on any object). module Findable def find(id) all.detect { |record| record.id == id } end def all @records ||= [] end end class User extend Findable end User.all # => [] User.find(1) # => nil user = User.new user.all # => NoMethodError Use extend when: You want the class itself to have the behavior. Combining Include and Extend A common pattern for modules that provide both: module Searchable def self.included(base) base.extend(ClassMethods) end module ClassMethods def search(query) all.select { |record| record.matches?(query) } end end # Instance methods def matches?(query) searchable_content.include?(query.downcase) end def searchable_content raise NotImplementedError, "Include searchable_content in #{self.class}" end end class Article include Searchable attr_accessor :title, :body def searchable_content "#{title} #{body}".downcase end end # Now we have both: Article.search("ruby") # Class method article.matches?("ruby") # Instance method Module Function: Both Ways module_function makes methods callable both on the module directly AND as private instance methods when included. module Formatting module_function def titleize(string) string.split.map(&:capitalize).join(' ') end def truncate(string, length: 50) return string if string.length <= length "#{string[0...length]}..." end end # Call on module directly Formatting.titleize("hello world") # => "Hello World" # Or include in a class class Document include Formatting def formatted_title titleize(@title) # Available as private method end end Use module_function when: You have pure utility functions that might be called directly on the module OR mixed into classes. The Quick Reference MechanismMethods becomeTypical use caseincludeInstance methodsShared object behaviorextendClass methodsShared class-level behaviormodule_functionBoth (module + private instance)Pure utilities Modern Idiom: ActiveSupport::Concern Rails popularized a cleaner pattern for combined include/extend: module Searchable extend ActiveSupport::Concern included do scope :search, ->(query) { where("content LIKE ?", "%#{query}%") } end class_methods do def most_searched # ... end end # Instance methods go directly in module body def matches?(query) content.include?(query) end end This is syntactic sugar but widely understood in the Rails world. Decision Framework: Bringing It All Together When starting with a new abstraction, walk through these questions: "I need to group some data" Is it truly immutable (conceptually)? → Data (if on Ruby 3.2+) Am I prototyping or is this temporary? → Struct Do I need validation or complex initialization? → Class Will it accumulate significant behavior? → Class "I need to share behavior" Is it an "is-a" relationship? → Inheritance (class) Is it a "has-capability" relationship? → Mixin (module with include) Are these pure functions with no state? → module_function Do I need both class and instance methods? → include + extend pattern "I need to organize code" Grouping related classes? → Module as namespace Avoiding name collisions? → Module as namespace Encapsulating an operation with dependencies? → Service object Simple operation on one object? → Method on that object "I'm not sure" Start simple. It's easier to graduate from Struct to class than to simplify an over-engineered class. Ruby's flexibility means you can refactor as understanding grows. Conclusion: Fit Over Fashion Ruby gives you many tools because different situations call for different solutions. The goal isn't to use the "best" tool in some abstract sense-it's to use the tool that best fits your current situation. Struct for quick data grouping and prototyping Data for immutable value objects Class for domain objects with behavior, validation, and growth potential Service objects for complex operations with multiple collaborators Module namespaces for organization and collision avoidance Module mixins for sharing capabilities across unrelated classes include/extend/module_function for controlling how shared behavior manifests Don't fear classes because someone said they're "heavy." Don't create service objects because it's "the pattern." Don't use mixins when inheritance is clearer. Choose based on intent. Your code should communicate not just what it does, but why it's structured the way it is. That's the Ruby way: pragmatic, expressive, and always in service of clarity. What patterns have you found most useful? I'd love to hear how you make these decisions in your own code. --- # A CTO Would Be Bored by Tuesday URL: https://www.ivanturkovic.com/2026/01/07/a-cto-would-be-bored-by-tuesday/ Published: 2026-01-07 Topics: Startups & Business Founder: "I need a CTO." Me: "For what?" Founder: "Technical leadership." Me: "What technical decisions are you making?" Founder: "Which tools to use. How to connect them. What to build vs buy." Me: "You need a technical advisor. Maybe 5 hours a month." Founder: "Not a full-time hire?" Me: "You're pre-product-market-fit with 2 clients. A CTO would be bored by Tuesday." This conversation happens more than you'd think. And every time, I watch the founder's face shift from confusion to relief. They came in thinking they needed a $250K hire. They left with a solution that actually fits their stage. The Expensive Mistake Nobody Talks About Last year, I got a call from a fintech founder. Let's call him Marcus. Marcus had just raised a seed round and his investors were pushing him to hire a CTO. "You need someone senior to own the technical vision," they said. Good advice in theory. Terrible advice for his situation. Marcus's startup had a working MVP built by a freelancer. Two paying customers. A burn rate that gave him 14 months of runway. And his investors wanted him to spend 30% of his raise on a single hire who'd spend most of their time... doing what, exactly? At that stage, the technical decisions that mattered could fit on a napkin: Should we rebuild the MVP or iterate on it? Which payment processor integrates best with our stack? Do we need to think about compliance infrastructure now or later? When should we hire our first full-time developer? These are important questions. But they're not 40-hours-a-week questions. They're 5-hours-a-month questions. I became Marcus's technical advisor. Six months later, his platform processed its first million dollars. His team grew to three developers. And he still calls me for the big decisions maybe 8 hours a month now. Total cost versus a full-time CTO? He saved enough to extend his runway by four months. The 3 AM Test Here's how I think about it. There are two types of technical leadership needs: The 3 AM problems. Your database is on fire. Production is down. Customers are screaming. You need someone who can SSH into a server, find the issue, and fix it while half-asleep. This is operational leadership. You need someone in the trenches, full-time. The Tuesday afternoon problems. Should we use PostgreSQL or MongoDB? Is this agency's quote reasonable? How do we structure our API for the integration we'll need in six months? This is strategic leadership. You need someone who's seen it before, but not necessarily someone sitting in your office every day. Early-stage startups have mostly Tuesday afternoon problems. They just don't realize it. I've been the 3 AM person. I've managed incidents at scale, led engineering teams through critical pivots, and built platforms serving millions of users. I know what a production crisis looks like. But I also know that most startups aren't there yet. When your entire user base fits in a spreadsheet, you don't need a full-time fire department. You need someone who knows where the extinguishers are. What I Actually Do (In 5-15 Hours a Month) Let me tell you about a typical month with one of my clients a SaaS company in the HR tech space. Week 1: 90-minute call reviewing their architecture decisions for a new feature. Their lead developer wanted to introduce microservices. I talked them out of it. Not because microservices are bad, but because they have four developers and one product. Premature optimization is a startup killer. Decision made, two hours saved in meetings they would have had without me. Week 2: Async review of three vendor proposals for their AI integration. I flagged that one vendor was promising capabilities that don't exist yet (a creative interpretation of what their LLM could do). Saved them from signing a contract they'd regret. Week 3: Technical due diligence call for a potential acquisition. The CEO wanted to buy a smaller competitor. I reviewed their codebase, identified $200K in technical debt they'd inherit, and helped negotiate the price down accordingly. Week 4: Sat in on a developer interview. Gave my read on the candidate. Suggested two questions that revealed they'd exaggerated their experience with React Native. Total time: 11 hours. Total value? They'd estimate it in the hundreds of thousands between the bad hire avoided, the contract renegotiated, and the architectural mistake prevented. The Skills I'm Lending You Twenty years in this industry means I've made mistakes you haven't made yet. That's the real product. I've shipped code across the full stack Ruby on Rails, Node.js, Python, React, React Native. I've built AI products before everyone was building AI products, integrating LLMs and vector databases when they were still considered experimental. I've done mobile (iOS, Android, cross-platform), infrastructure (AWS, GCP, Kubernetes), and even blockchain when that was the thing everyone wanted to do. But here's what actually matters: I've been through the full cycle. Founded startups. Scaled them. Watched them fail. Learned why. Built the next one better. I know what a Series A-ready architecture looks like because I've built them. I know what technical debt kills companies because I've seen the autopsies. I know which corners you can cut and which ones will haunt you at 3 AM six months from now. This isn't consulting. It's borrowing experience. The Honest Truth About When You Need Me Not every company needs a fractional CTO. Here's my honest take: You need me if: You're a non-technical founder making technical decisions that feel like educated guesses You have developers but no one to validate their architectural choices You're evaluating vendors, agencies, or potential technical hires and need a bullshit detector You're approaching a decision that will define your next 2-3 years and want someone who's seen the outcomes before You need AI capabilities integrated into your product but don't know where to start Your current architecture won't survive the next stage of growth You don't need me if: You need someone writing code 40 hours a week You're looking for the cheapest option (I'm not, and experience isn't cheap) You want someone to just follow specs without questioning them Your technical decisions are straightforward and your team handles them fine I'd rather tell you that you don't need me than take your money for something you don't need. Bad fits waste everyone's time. What This Looks Like in Practice I structure my advisory work around what you actually need: Technical Advisor (5-10 hours/month): Regular calls, async access for urgent questions, architecture reviews, vendor evaluations, hiring support. Best for early-stage startups or companies with junior technical teams. Fractional CTO (10-20 hours/month): Everything above, plus deeper involvement in roadmap planning, team structure, investor communications, and strategic technical decisions. Best for scaling companies that need senior technical leadership but aren't ready for a full-time executive. Project-Based: Technical due diligence for acquisitions or investments. Architecture overhauls. AI integration strategy. Best when you have a specific challenge with a clear endpoint. I'm currently working with clients across fintech, AI/ML, SaaS, and blockchain companies that need someone who speaks both business and code fluently. Someone who's pitched to investors and debugged production issues in the same week. The Conversation That Should Have Happened Back to Marcus, the fintech founder. A year later, he actually did hire a CTO. A great one. But here's what he told me: "If I'd hired someone full-time when my investors pushed me to, I would have hired the wrong person. I didn't even know what I needed yet. Working with you helped me understand what 'technical leadership' actually means for my company. Now I know exactly what to look for." That's the real value. Not replacing a CTO. Helping you get ready for one or realizing you don't need one yet. Right resource. Right stage. I'm currently taking on 2-3 new advisory clients. If you're facing technical decisions that feel bigger than your current resources, let's talk. I'll tell you honestly whether I can help or whether you need something different entirely. Get in touch or connect with me on LinkedIn. Know a founder who's wrestling with the "do I need a CTO?" question? Share this with them. --- # What I Wrote About in 2025 URL: https://www.ivanturkovic.com/2025/12/29/what-i-wrote-about-in-2025/ Published: 2025-12-29 Topics: AI, Career & Leadership, Ruby on Rails, Software Engineering Looking back at the year, my blog became a running commentary on how AI is fundamentally reshaping software development, and not always in the ways people expect. I've been splitting my attention between technical deep-dives and broader observations about where this whole industry is heading. Here's what caught my attention month by month. March 2025: Learning to Work With AI Without Losing Your Skills Most Common Mistakes Developers Make With AI-Assisted Coding (And How to Fix Them) In March, I tackled something that was bothering me about the explosion of AI coding tools. Everyone was celebrating how fast they could ship code, but nobody was talking about the mistakes piling up underneath. I wrote about the most common mistakes developers were making with AI-assisted coding, and more importantly, how to actually get better at working with these tools instead of just letting them do everything. The piece focused on practical stuff like understanding what the AI is actually generating, verifying its output, and not treating it like a magic black box. Because here's the thing: AI can write code faster than you can, but it can't understand your business logic, your edge cases, or why that weird workaround exists in your codebase. That's still on you. April 2025: Why AI Won't Replace Software Engineers (And Might Create More Jobs) Why AI Coding Tools Will Increase Demand for Software Engineers, Not Decrease It By April, the "AI will replace all developers" narrative was reaching fever pitch, so I pushed back with something that seemed counterintuitive at the time but makes perfect sense if you think about it: AI coding tools are going to increase demand for software engineers, not decrease it. The reasoning is simple. When you make it easier to build software, more people try to build software. When more people build software, you get more software that needs to be maintained, scaled, secured, and actually made to work properly. And guess who's going to do all that? Not the AI. It'll be DevOps engineers, MLOps specialists, and senior developers who can clean up the mess. I called it "vibe coding" when people use AI to generate an app based on a feeling of what they want, without understanding what they're actually building. That creates opportunities for people who do understand what's happening under the hood. May 2025: The Gap Between Junior and Senior Developers Just Got Wider How AI Coding Tools Are Widening the Gap Between Junior and Senior Developers In May, I wrote about something uncomfortable that people weren't talking about openly: AI isn't leveling the playing field between junior and senior developers like everyone hoped. It's actually making the gap wider. Senior developers use AI to move faster because they know what to ask for, how to verify the output, and when to ignore bad suggestions. Junior developers use AI as a crutch, never developing the intuition they need to become good engineers. They can ship code, sure, but they can't debug it, explain it, or maintain it. This wasn't meant to be doom and gloom about juniors. It was a wake-up call that we need to rethink how we mentor and train people in an AI-assisted world. Just having AI available doesn't make you a better developer. It can make you worse if you're not careful. October 2025: The 200,000 Lines of AI Code That Didn't Matter A Team Deleted 200,000 Lines of AI-Generated Code and the App Still Works October brought one of my favorite stories of the year. A team deleted 200,000 lines of AI-generated code and their application kept working fine. Not just working, but actually running better in some ways. This perfectly illustrated what I'd been saying all year: AI can generate massive amounts of code, but that doesn't mean the code is necessary, good, or even doing what you think it's doing. The team had been using AI to rapidly build features, and they ended up with this bloated codebase that nobody understood. When they finally went through it systematically, they found that most of it was either redundant, wrong, or solving problems that didn't exist. The real lesson wasn't "AI bad" but "engineering discipline still matters." AI can accelerate your development, but it can also accelerate your chaos if you don't have good practices in place. November 2025: Why PgVector Matters for AI Applications Why PgVector Is Essential for AI Products in 2025 In November, I got more technical and wrote about PgVector, a PostgreSQL extension for handling vector embeddings. This was me diving into the practical side of building AI products, not just theorizing about them. If you're building anything with large language models that needs memory or context, you need a way to store and retrieve vector embeddings efficiently. PgVector gives you that inside PostgreSQL, which means you don't need to add another database or service to your stack. You can keep everything in your existing Postgres setup. I made the point that in 2025, if you're building AI products, PgVector isn't a nice-to-have. It's a core architectural component. The ability to give LLMs relevant context, personalize responses, and reduce hallucinations depends on good vector search, and PgVector handles that elegantly. December 2025: Getting Philosophical About Work, Culture, and What Actually Matters December saw me branching out from purely technical topics into bigger questions about work culture and organizational behavior. I wrote several pieces that questioned common assumptions about corporate life. Company Culture: I challenged the entire concept of "corporate culture," arguing that most of what companies call culture is just performance art for executives. Real culture emerges organically from how people actually work together, not from mission statements and team-building exercises. When it comes to company culture, there's really nothing cultural about it by itself at all. It's just management dressed up in anthropological language. The Corporate Culture Charade Part 2: How AI Is Killing What Little Culture We Had Left: In a follow-up piece, I explored how AI is killing what little authentic culture companies had left. Everyone is using AI to write emails, generate reports, and summarize meetings. Those summaries get fed into more AI systems, creating this closed loop where nobody is actually thinking anymore. We're just passing AI-generated content through human intermediaries, and calling it work. Stop Procrastinating in 2025: Part 1 and Part 2: From Intentions to Impact: I wrote a two-part series about actually making changes in your life instead of just writing resolutions that fail by February. Part 1 focused on fixing your procrastination and distraction problems before you even think about goals. Part 2 was about building a real operating system for your life, with themes instead of rigid goals and quarterly planning instead of annual pipe dreams. A Christmas Eve Technology Outlook: Ruby on Rails and Web Development in 2026: Right before Christmas, I published a long piece about Ruby on Rails and web development heading into 2026. Rails is having a renaissance, powered by Ruby 4.0's performance improvements and Rails 8's focus on simplicity. The framework that people kept declaring dead is thriving because it prioritized developer happiness over chasing every new trend. The Future of Language Frameworks in an AI-Driven Development Era: I also explored whether we even need traditional programming conventions anymore when AI is writing most of the code. The answer is yes, we still need them, because humans still need to read, understand, and maintain the code. But the way we think about framework design might need to evolve to support both human developers and AI code generation. Building a Decentralized Credit Card System Part 2: Solidity Smart Contract Implementation: I published a technical piece about building a decentralized credit card system using smart contracts, exploring how multi-signature controls and encrypted spending limits could work on the blockchain. It was a deep dive into Solidity implementation, showing actual production-ready code for a credit facility contract. What Ties It All Together Looking across the whole year, there's a thread connecting everything I wrote. It's about maintaining sanity and competence while technology moves faster than our ability to process what's happening. AI is powerful. It's going to change how we build software, how we work, maybe how we think. But the fundamentals still matter. You still need to understand what you're building. You still need good engineering practices. You still need to think for yourself instead of outsourcing everything to machines. Companies are still going to waste time on culture initiatives instead of paying people fairly and giving them meaningful work. People are still going to procrastinate and make resolutions they won't keep unless they fix the underlying systems first. Frameworks like Rails are still going to matter because they make developers productive and happy. The technology changes. The human challenges remain surprisingly constant. That's what I've been writing about in 2025. Not just what's new, but what actually matters once you get past the hype. And trying to do it in a way that's honest about both the opportunities and the problems we're creating for ourselves. Looking ahead to 2026, I expect more of the same, but more intense. AI will get better at coding, which means the need for humans who can think clearly about code will become even more critical. Companies will continue to miss the point about what makes people effective. And somewhere in there, I'll keep writing about it, trying to make sense of where we're all heading. That's the year. Thanks for reading. --- # A Christmas Eve Technology Outlook: Ruby on Rails and Web Development in 2026 URL: https://www.ivanturkovic.com/2025/12/24/a-christmas-eve-technology-outlook-ruby-on-rails-and-web-development-in-2026/ Published: 2025-12-24 Topics: AI, Ruby on Rails As we gather with loved ones this Christmas Eve, wrapping presents and reflecting on the year behind us, it's the perfect moment to gaze into the technology crystal ball and envision what 2026 holds for web development and particularly for Ruby on Rails, the framework that's been delighting developers for over two decades. While children dream of what Santa might bring tomorrow morning, we developers can't help but wonder: what gifts will the tech world deliver in the year ahead? Spoiler alert: 2026 looks to be one of the most transformative years in Rails history. The State of Rails as We Enter 2026 Ruby on Rails enters 2026 in a fascinating position. After years of obituaries prematurely declaring the framework dead, Rails has experienced a remarkable renaissance. The community is energized, adoption is growing, and most importantly, Rails is evolving faster than it has in years. Rails 8, which recently launched, brought us significant improvements in deployment simplicity, background job processing with Solid Queue, and database-backed caching with Solid Cache. But these aren't just incremental improvements, they represent a philosophical shift toward making Rails deployment radically simpler and more cost-effective. The framework that once required complex infrastructure setups can now be deployed to a single server with SQLite handling everything from the primary database to job queues and caches. This isn't a step backward to simplicity, it's a leap forward to sophistication without complexity. AI-Powered Development: Rails' Secret Weapon Here's what might surprise you: Rails is uniquely positioned to thrive in an AI-driven development world. While newer frameworks chase the latest JavaScript patterns, Rails' convention-over-configuration philosophy and opinionated structure make it remarkably AI-friendly. Large language models like Claude, GPT-4, and GitHub Copilot excel at working with Rails because the framework's conventions are well-documented and consistent. When an AI generates Rails code, it's working within a predictable structure that's been refined over two decades. In 2026, expect to see: AI-powered Rails generators that understand context. Instead of running rails generate scaffold User, you'll describe your entire domain model in natural language, and AI will generate not just the scaffolding but intelligent relationships, validations, and business logic. Intelligent code review and refactoring. AI tools will analyze your Rails codebase and suggest architectural improvements, identify N+1 queries, and recommend better patterns in all while maintaining Rails conventions. Natural language database queries. Rather than writing ActiveRecord queries, developers will increasingly describe what they want in plain English, with AI translating to optimal database queries. Automated test generation. AI will generate comprehensive test suites that actually understand your business logic, not just achieve code coverage percentages. The Rails community is already embracing these tools faster than many other ecosystems, and 2026 will see this adoption accelerate dramatically. The Hotwire Revolution Matures If 2024 and 2025 were the years Hotwire proved itself, 2026 will be the year it fully matures into the dominant pattern for Rails applications. Hotwire he combination of Turbo and Stimulus has fundamentally changed how we build interactive web applications with Rails. The beauty of Hotwire is that it lets you build rich, reactive applications while writing primarily server-side code. No separate API layer, no complex state management, no duplicated validation logic. Just Rails doing what Rails does best: rapid development of maintainable applications. In 2026, we'll see: Turbo 8+ bringing desktop-class interactions. Enhanced morphing capabilities, smoother animations, and better handling of complex UI states. The line between traditional web apps and single-page applications will blur further. Stimulus 4 with improved TypeScript support. The lightweight JavaScript framework will continue to evolve, making it even easier to add just enough interactivity without drowning in JavaScript complexity. Mainstream adoption of Turbo Native. More Rails shops will embrace Turbo Native for building iOS and Android apps that share a codebase with their web applications. The dream of true write-once, run-everywhere is becoming practical. Visual Hotwire builders. Tools that let you design Turbo Frame interactions visually, generating the corresponding Rails code automatically. The developer experience with Hotwire is already excellent, but 2026 will bring the tooling and community resources to make it the obvious choice for any new Rails project. Rails Meets Modern DevOps: Deployment Simplified One of the most exciting trends for 2026 is the radical simplification of Rails deployment. For years, deploying Rails apps meant wrestling with complex infrastructure, multiple services, and expensive hosting bills. That era is ending. The single-server renaissance. Modern servers are incredibly powerful. Rails 8's focus on making it practical to run everything on one server web app, database, job processor, cache will become mainstream in 2026. We're not talking about toy apps; we're talking about applications serving millions of requests. SQLite in production gains serious momentum. Yes, you read that correctly. With Litestack and improvements in SQLite itself, Rails apps running on SQLite will handle production loads that would have seemed impossible a few years ago. The cost savings and operational simplicity are too compelling to ignore. Kamal 2.0 and beyond. The deployment tool that ships with Rails 8 will continue to evolve, making it trivial to deploy Rails apps to any server with Docker. Complex Kubernetes configurations will increasingly seem like overkill for most Rails applications. Edge computing integration. Rails will get better at running on edge infrastructure like Fly.io and Railway, bringing your application closer to users worldwide without the operational complexity. This doesn't mean Rails is abandoning scalability it means we're recognizing that most applications don't need the complexity we've been building into them. Start simple, scale only when needed, and only add complexity when you actually require it. ViewComponent and Modern Frontend Architecture The way we structure Rails views is evolving rapidly, and 2026 will see this evolution accelerate. ViewComponent, the framework for building reusable, testable view components, is changing how we think about frontend code in Rails. Component-driven development becomes standard. Just as React normalized component thinking for JavaScript apps, ViewComponent is bringing the same patterns to Rails with the benefit of server-side rendering and better performance. Design system integration. Companies will ship entire design systems as ViewComponent libraries, making it trivial to maintain visual consistency across large Rails applications. AI-generated components. Describe a UI pattern, and AI will generate the ViewComponent implementation, complete with tests and Tailwind CSS classes. Better preview and testing tools. The developer experience around ViewComponent will continue to improve with better preview frameworks, visual regression testing, and component documentation tools. This represents Rails evolving its frontend story without abandoning its server-side roots. You get the benefits of component architecture without the JavaScript framework overhead. Ruby 4.0: The Christmas Gift That Just Arrived Here's the perfect Christmas miracle for the Rails ecosystem: Ruby 4.0 is arriving right now. With preview3 released on December 18, 2025, and the full release anticipated on Christmas Day 2025 Ruby's 30th anniversary this is the gift that will keep giving throughout 2026 and beyond. Ruby 4.0 isn't just an incremental update. It represents a significant leap forward for the language, with the new ZJIT (Zero-overhead Just-In-Time) compiler leading the charge. This is the performance boost the entire ecosystem has been waiting for. ZJIT: A Game-Changing JIT Compiler. The new just-in-time compiler promises dramatic performance improvements with minimal overhead. Early benchmarks show significant speedups across the board, making Rails applications faster without changing a single line of code. Better memory management. Improvements to the garbage collector will reduce memory usage and eliminate many performance bottlenecks that have plagued Ruby applications for years. Modern concurrency primitives. Ruby 4.0 brings better tools for concurrent programming, making it easier to write high-performance Rails applications that take full advantage of modern multi-core processors. Type checking maturity. While Ruby remains dynamically typed, the type checking story with RBS and TypeProf has improved significantly, giving developers better tools without sacrificing Ruby's flexibility. What makes this timing so perfect is that Rails developers will spend all of 2026 discovering and leveraging these improvements. The performance gains alone will change conversations about Rails scalability, and the improved tooling will make the development experience even better. The Security and Privacy Advantage In an era of increasing data breaches and privacy concerns, Rails' security-by-default philosophy becomes a significant competitive advantage. Rails has always made it easy to build secure applications, and 2026 will see this advantage become even more pronounced. Built-in privacy controls. Expect better tools for handling GDPR, CCPA, and other privacy regulations directly in Rails, making compliance less painful. Enhanced authentication and authorization. The Rails authentication story will continue to improve with better generators and patterns for common security scenarios. Security scanning in development. AI-powered tools will analyze your Rails code for security vulnerabilities in real-time, suggesting fixes before code reaches production. Zero-trust architecture patterns. Rails will make it easier to implement zero-trust security models, with better tools for service-to-service authentication and authorization. As privacy regulations tighten globally, frameworks that make security easy by default will have a significant advantage. Rails is well-positioned here. The Rails 8.1 and 8.2 Roadmap Looking at what's coming in Rails point releases through 2026, several exciting features are on the horizon: Progressive Web App support. Better tools for building PWAs that work offline and provide app-like experiences. Real-time collaboration primitives. Building features like collaborative editing will become significantly easier with new abstractions in Rails. Better observability. Improved logging, metrics, and tracing capabilities built directly into the framework. GraphQL integration improvements. For teams that need GraphQL, Rails will provide better first-party support. Performance monitoring enhancements. Better tools for identifying and fixing performance issues in development. These aren't revolutionary changes they're the kind of steady improvements that make Rails better every release. The Broader Web Development Landscape Rails doesn't exist in a vacuum, and understanding the broader trends helps predict how Rails will evolve: The JavaScript framework fatigue continues. Developers are tired of the constant churn in JavaScript frameworks. Rails' stability becomes increasingly attractive as people realize they don't need bleeding-edge frontend frameworks for most applications. Monoliths make a comeback. After years of microservices hype, the industry is recognizing that monoliths especially well-structured Rails monoliths are often the better choice. Rails is perfectly positioned for this shift. SQLite gains enterprise credibility. The database long dismissed as a toy will increasingly be seen as a legitimate choice for production applications, and Rails is leading this charge. AI changes how we evaluate frameworks. The ease of AI code generation becomes a significant factor in framework choice. Rails' predictability and conventions make it a winner here. Developer experience wins. After years of prioritizing performance and scalability above all else, the industry is rediscovering that developer happiness matters. Rails has always prioritized this. What This Means for Rails Developers If you're a Rails developer, 2026 looks incredibly promising. Here's what you should focus on: Embrace Hotwire fully. If you haven't already, 2026 is the year to go all-in on Turbo and Stimulus. The ecosystem is mature enough now that there's no reason to hesitate. Learn AI-assisted development. Get comfortable with AI coding assistants. They're not replacing you they're making you more productive. Rails developers who master AI tools will have a significant advantage. Simplify your deployments. Stop over-engineering your infrastructure. Embrace the single-server renaissance and spend your time building features, not managing DevOps complexity. Invest in ViewComponent. If you're building any substantial Rails application, adopting component-driven development with ViewComponent will pay dividends. Stay current with Rails. The pace of Rails evolution is accelerating. Staying within one major version of current is increasingly important. What This Means for CTOs and Technical Leaders If you're making technology choices for your organization, Rails in 2026 offers compelling advantages: Lower operational costs. Simpler deployment models mean smaller DevOps teams and lower hosting costs. Faster time-to-market. Rails' productivity advantages are being amplified by AI tools, meaning your team can ship features faster than ever. Easier hiring and retention. Rails developers tend to be senior and pragmatic. The community values craft and sustainability over hype. Long-term stability. Rails isn't going anywhere. Betting on Rails in 2026 is betting on a framework that will still be thriving in 2036. AI-ready architecture. Rails applications are inherently well-suited for AI code generation and modification, making it easier to leverage AI in your development process. The Contrarian View: Challenges Ahead It wouldn't be honest to paint an entirely rosy picture. Rails faces real challenges in 2026: JavaScript ecosystem fragmentation. While Hotwire is excellent, some teams will continue to prefer React or Vue, and integrating these with Rails can be awkward. Performance perception. Despite Rails being fast enough for most applications, the perception that it's slow persists and affects adoption. Learning curve for modern Rails. The framework has added significant complexity with Hotwire, ViewComponent, and other modern patterns. New developers face a steeper learning curve than in the past. Competition from Go and Rust. For performance-critical applications, languages like Go and Rust offer compelling alternatives. Corporate backing concerns. Rails doesn't have the corporate backing of frameworks like Next.js (Vercel) or Laravel (Laravel Inc.), which can impact ecosystem development speed. These challenges are real, but they're also manageable. Rails has faced existential threats before and emerged stronger each time. The Wildcard: What Could Change Everything Every year brings surprises, and 2026 will be no different. Here are some wildcard scenarios that could dramatically impact Rails: Apple or another major tech company adopts Rails for a flagship service. This would bring renewed attention and resources to the ecosystem. A breakthrough in Ruby performance. If Ruby 5.0 or another development brings 5-10x performance improvements, it changes the entire conversation around Rails scalability. A major Rails-powered IPO. If a company like Shopify or GitHub has an especially successful year, it reminds the market that Rails powers major businesses. AI code generation reaches a tipping point. If AI gets good enough to generate entire Rails applications from descriptions, it could dramatically accelerate Rails adoption. A security incident in a major JavaScript framework. This could accelerate the trend toward server-rendered applications and benefit Rails. Wrapping Up: A Gift Worth Unwrapping As Christmas Eve turns to Christmas morning and we open gifts with family, it's worth remembering that the best gifts aren't always the flashiest or most expensive. Sometimes the best gift is something reliable, something that brings joy, something that just works. That's Ruby on Rails in 2026. It's not the newest framework or the hottest trend. It's something better: a mature, stable, productive framework that's evolving thoughtfully while staying true to its core values. Rails in 2026 will be faster, simpler to deploy, more AI-friendly, and more productive than ever before. The framework that started a revolution in 2004 with its focus on developer happiness is starting another revolution proving that you don't need microservices, complex build toolchains, and separate frontend frameworks to build world-class web applications. For developers who value craft, productivity, and sustainability over hype, 2026 is going to be a very good year indeed. The Rails community is energized, the framework is evolving rapidly, and the broader industry trends are aligning in Rails' favor. So as you celebrate this Christmas Eve with loved ones, raise a glass to the year ahead. Whether you're building a startup MVP, scaling a growing business, or maintaining enterprise applications, Rails has something to offer you in 2026. The framework that lets you go from idea to deployed application in a weekend isn't going anywhere. It's just getting better, faster, and more delightful to work with. Merry Christmas, and happy coding in 2026. May your deploys be smooth, your tests be green, and your applications bring joy to users around the world. From my family to yours, wishing you a Merry Christmas and a prosperous new year building with Ruby on Rails. What are you most excited about for Rails in 2026? What features do you hope to see? Share your thoughts in the comments below, and let's build the future of web development together. --- # The Future of Language Frameworks in an AI-Driven Development Era URL: https://www.ivanturkovic.com/2025/12/23/the-future-of-language-frameworks-in-an-ai-driven-development-era/ Published: 2025-12-23 Topics: AI As artificial intelligence increasingly writes the code that powers our digital world, we're standing at a fascinating crossroads in software development history. The fundamental question looming over our industry is deceptively simple yet profoundly complex: if AI is writing our code, do we still need the elaborate conventions, configurations, and architectural patterns that have defined programming for decades? The Human-Centric Origins of Programming Conventions To understand where we're heading, we need to appreciate where we came from. Every convention in modern programming exists because of human limitations and needs. We created naming conventions because humans struggle to parse meaning from abbreviated variable names. We built framework architectures with separation of concerns because human brains can only hold so many concepts simultaneously. We established coding standards because teams of humans need consistent patterns to collaborate effectively. Consider React's component lifecycle methods or Django's MTV pattern. These weren't designed for machines; they were designed to help human developers reason about complex state management and data flow. The elaborate configuration files we maintain, from webpack.config.js to build.gradle, exist primarily because humans need explicit, readable instructions to understand how systems connect. The AI Shift: Writing Code Without Human Constraints Large language models like GPT-4, Claude, and their successors are fundamentally changing this calculus. These systems don't experience cognitive load the way humans do. An AI can effortlessly track hundreds of variables across thousands of lines of code without losing context. It doesn't need mnemonic variable names or carefully structured modules to maintain understanding. This raises provocative questions about framework design. If AI is generating the majority of code, why maintain the extensive boilerplate that frameworks require? Why enforce file structure conventions when an AI can navigate any organizational pattern with equal ease? Why maintain backwards compatibility with legacy APIs when AI can seamlessly translate between different versions? The answer isn't as straightforward as simply abandoning all conventions. Instead, we're likely entering an era of dual-purpose frameworks systems designed to be both AI-generatable and human-readable, though with increasing emphasis on the former. The Emergence of AI-Native Frameworks We're already seeing early signals of this transformation. New frameworks and libraries are emerging with characteristics that would have seemed bizarre a decade ago: Minimal configuration through intelligent defaults. Rather than requiring developers to specify every detail, AI-native frameworks make sophisticated assumptions about intent, allowing AI systems to generate working code with far less explicit configuration. Natural language-driven APIs. Instead of memorizing specific method signatures, developers and AI systems can describe desired functionality in plain language, with the framework interpreting intent and executing accordingly. Self-documenting architectures. Code that explains its own purpose and functionality through embedded semantic information that both humans and AI can parse. Dynamic optimization. Frameworks that automatically restructure themselves based on usage patterns and performance characteristics, without requiring manual refactoring. We're witnessing the birth of tools like Cursor, GitHub Copilot Workspace, and other AI-assisted development environments that treat code as a malleable medium rather than fixed text. These systems suggest that future frameworks might be less about rigid structure and more about expressing intent that AI can flexibly interpret. The Case for Keeping Human-Centric Conventions Before we rush to tear down the edifice of software engineering practices, there are compelling reasons to maintain many existing conventions, at least for the foreseeable future: Human oversight remains essential. Even as AI writes more code, humans still review, audit, and maintain these systems. Code that's optimized purely for AI generation but incomprehensible to humans creates dangerous blind spots in critical systems. Edge cases and domain expertise. AI excels at pattern matching but can struggle with truly novel problems or highly specialized domains. Human developers with deep expertise still need to intervene, and they need frameworks they can understand. Security and reliability. Code that humans can't audit is code that can harbor vulnerabilities indefinitely. Security-critical systems require human-readable implementations where experts can verify correctness. Collaboration between AI and humans. The most productive development workflows involve humans and AI working together. Frameworks need to support this hybrid model, not optimize for one at the expense of the other. Educational and onboarding value. New developers still need to learn programming concepts. If frameworks become too abstracted or AI-dependent, we risk creating a generation gap where fewer people understand the underlying systems. The Birth of New Languages: A Thought Experiment If we're rethinking frameworks, why not rethink languages themselves? The programming languages we use today Python, JavaScript, Java, Rust were all designed with human cognition as the primary constraint. An AI-native programming language might look radically different. Imagine a language where: Verbosity is encouraged rather than minimized. Descriptive, self-documenting code that reads almost like natural language prose, because the AI doesn't mind typing lengthy expressions. Implicit typing is taken to extremes. The AI infers types from usage patterns across entire codebases, eliminating type annotations entirely while maintaining type safety. Non-linear code organization. Rather than top-to-bottom execution, code chunks declare dependencies and intents, with the AI determining optimal execution order. Probabilistic error handling. Instead of explicit try-catch blocks, the language allows developers to specify acceptable failure rates and desired behaviors, with the AI generating appropriate error handling code. Some of these concepts are already emerging in experimental languages and DSLs (domain-specific languages). But mainstream adoption faces significant hurdles, primarily the enormous investment in existing codebases and the developer expertise tied to current languages. The Practical Path Forward: Evolution, Not Revolution Despite the theoretical possibilities, the practical evolution of frameworks and languages will likely be gradual rather than revolutionary. Here's what we're more likely to see in the next five to ten years: Hybrid frameworks with dual interfaces. Frameworks that offer both traditional APIs for human developers and AI-optimized interfaces for code generation. Developers choose their level of abstraction based on the task. AI-assisted migration tools. Rather than abandoning legacy frameworks, we'll see sophisticated tools that help AI systems understand and work with existing codebases, automatically applying modern patterns and optimizations. Convention compression. Many existing conventions will be consolidated or automated away. Configuration files might be replaced by intelligent systems that infer settings from context. Boilerplate code will increasingly be generated on-demand rather than manually written. Framework transparency layers. Middleware systems that sit between AI code generation and framework execution, translating AI-generated patterns into framework-compliant implementations automatically. Semantic versioning on steroids. Version management systems that use AI to automatically update code when frameworks change, making breaking changes far less disruptive. What This Means for Framework Developers If you're building frameworks or libraries today, the AI revolution demands rethinking your design priorities: Optimize for intent expression over execution detail. Allow developers and AI to specify what they want to accomplish, not just how to accomplish it. Build comprehensive semantic models. Frameworks need machine-readable descriptions of their capabilities, constraints, and intended usage patterns. Documentation becomes first-class code. Design for code generation. Provide clear patterns that AI systems can reliably reproduce. Avoid clever tricks or implicit behaviors that work well for experienced human developers but confuse code generation systems. Embrace automated testing at scale. As AI generates more code, testing frameworks need to automatically verify correctness across a vastly expanded solution space. Plan for continuous evolution. Static APIs are becoming obsolete. Frameworks need mechanisms for graceful evolution that don't break the AI systems depending on them. The Role of Standards and Governance As frameworks and languages evolve in this AI-driven direction, questions of governance and standardization become critical. Who decides how AI-native frameworks should behave? What prevents a fragmented ecosystem where every AI system expects different conventions? We'll likely see the emergence of new standards bodies focused specifically on AI-code interaction protocols. These organizations will establish common patterns for how AI systems should generate, document, and maintain code across different frameworks and languages. The companies developing the most powerful AI coding assistants Anthropic, OpenAI, Google, Microsoft will have outsized influence on these standards simply because frameworks that work well with their tools will gain adoption advantages. The Enduring Value of Simplicity Throughout this transformation, one principle seems likely to endure: simplicity matters. Even if AI can handle immense complexity, there's value in systems that do one thing well with minimal dependencies and clear interfaces. The Unix philosophy of small, composable tools may find new relevance in an AI-driven world. AI systems excel at combining simple components into complex solutions. Frameworks that provide clear, atomic capabilities might prove more valuable than monolithic systems trying to anticipate every need. This suggests that while we may see revolutionary changes in how we configure and use frameworks, the underlying engineering principle of managing complexity through abstraction and modularity will remain essential. Conclusion: Conventions Serve the Maintainer Ultimately, the question of whether we need traditional conventions in an AI-written code world comes down to this: who maintains the code, and what do they need to do their job effectively? If we reach a future where AI not only writes but also maintains, debugs, and evolves code with minimal human intervention, then yes, many human-centric conventions may fade away. We might see the rise of code that's optimized purely for machine interpretation, with human-readable representations generated on-demand when humans need to intervene. But we're not there yet, and may never fully arrive. Code intersects with business logic, regulatory requirements, ethical considerations, and strategic decisions that require human judgment. Even the most sophisticated AI remains a tool serving human purposes. The frameworks and languages of the next decade will likely occupy a middle ground more AI-friendly than what we have today, but still anchored in principles of clarity, maintainability, and human comprehension. We'll automate away tedious conventions while preserving the essential structures that make software understandable and trustworthy. The birth of new frameworks and languages will accelerate as AI removes barriers to experimentation. We'll see more rapid prototyping, faster evolution, and more specialized solutions for specific domains. But the fundamental challenge remains unchanged: building systems that reliably solve real problems while remaining comprehensible to the humans who depend on them. As we navigate this transition, the most successful developers, architects, and framework designers will be those who can think in both paradigms understanding what humans need to work effectively while also grasping what AI systems can accomplish. The future of programming isn't about choosing between human and machine conventions. It's about finding the elegant synthesis where both can thrive. --- # From Intentions to Impact: Your 2025 Strategy Guide (Part 2) URL: https://www.ivanturkovic.com/2025/12/22/from-intentions-to-impact-your-2025-strategy-guide-part-2/ Published: 2025-12-22 Topics: Career & Leadership, Software Engineering The Resolution Graveyard It's December 22nd. In nine days, millions of people will make promises to themselves that they won't keep. They'll join gyms they'll stop visiting by February. They'll buy courses they'll never finish. They'll write goals in fresh notebooks that will gather dust by March. Why? Because they skipped Part 1. If you haven't read Part 1 of this series, go back and do the work. Seriously. Building a goal system on top of broken productivity habits is like trying to run a marathon while smoking a pack a day. But if you've spent this past week implementing the foundation blocking distractions, building focus systems, reclaiming your attention then you're ready for this conversation. Part 2 is about what comes next: turning your reclaimed time into a life that actually matters. The Hard Truth About New Year's Resolutions Let me share something uncomfortable: I've written New Year's resolutions every year since 2010. Fifteen years of goal-setting. Want to know how many I actually achieved? About 30%. Not because I'm lazy. Not because the goals were wrong. But because I was playing the wrong game entirely. Traditional New Year's resolutions fail for three reasons: 1. They're Outcome-Focused Instead of System-Focused "Lose 20 pounds" is an outcome. What's your system? How will you actually do it? Most people have no idea, so they wing it and wonder why willpower runs out by January 15th. 2. They're Disconnected From Your Actual Life You write goals in a moment of inspiration, usually after a few drinks on New Year's Eve or in a burst of motivation on January 1st. Then reality hits. Your job doesn't care about your goals. Your family doesn't care about your goals. Life keeps happening, and your resolution list sits in a drawer. 3. They're Based on Who You Think You Should Be, Not Who You Are Society says you should work out, eat healthy, read more, learn a language, start a side hustle. So you write all of those down. Six goals that have nothing to do with what you actually want or need. The New Model: Life Operating Systems Forget resolutions. What you need is a Life Operating System. Think about it: Your computer has an operating system (macOS, Windows, Linux) that manages resources, runs applications, and handles everything in the background. Your phone has one too. But your life? Most people are running on whatever habits they accidentally developed, reacting to whatever comes their way, with no intentional design. A Life Operating System is different. It's the framework that runs in the background of your life, making sure your time, energy, and attention flow toward what matters. Here's how to build one for 2025. Step 1: The Annual Review (Before You Plan Forward) You cannot plan where you're going without understanding where you've been. Block 3-4 hours on December 27-29 for this exercise. Find a quiet place. No phone. Just you, a notebook, and honest reflection. The Five Questions That Matter 1. What worked in 2024? Not what you wished worked. What actually worked? What projects succeeded? What habits stuck? What decisions paid off? Write everything down. Small wins count maybe you started meal prepping on Sundays, or you finally deleted Instagram, or you had one really good quarter at work. Why this matters: Your successes contain your operating instructions. You're trying to find patterns in what works for you, not what works for Tony Robbins or some productivity guru. 2. What didn't work in 2024? This is harder. It requires honesty. What failed? What did you start and quit? What goal did you write down and completely ignore? More importantly: Why didn't it work? Was the goal wrong for you? Was the timing wrong? Did you lack skills/resources? Did you not actually want it? Was it someone else's goal for you? Example from my 2024 review: I wanted to post on LinkedIn every day. Lasted three weeks. Why? Because I hate performative content. The goal wasn't wrong building audience matters. But the system was wrong for my personality. 3. What surprised you in 2024? This question is pure gold. Surprises reveal assumptions you didn't know you had. Maybe you thought you hated exercise but discovered you love bouldering. Maybe you thought you needed to work 60 hours a week but your best quarter came when you worked 40. Maybe AI tools changed your work more than you expected. 4. What drained you in 2024? Energy is finite. What consistently left you exhausted, frustrated, or depleted? Certain clients? Specific types of work? Toxic relationships? Doom-scrolling news? These are your energy leaks. You can't fix them all, but awareness is the first step. 5. What made you come alive in 2024? When did time fly? When were you energized instead of drained? When did you feel most like yourself? These moments are breadcrumbs pointing toward your zone of genius. Follow them. The Quantitative Audit Feelings matter, but data doesn't lie. Pull these numbers: Financial: Income, expenses, savings rate, debt (if any) Health: Weight, basic health metrics, doctor visits Relationships: Count of deep conversations, time with close friends/family Skills: Courses completed, books read, skills acquired Career: Promotions, raises, projects shipped, value created Time: Average work hours, vacation days taken, screen time You don't need perfect tracking. Estimates are fine. The point is to ground your review in reality, not just feelings. Pro tip: If you can't estimate these numbers, that's data itself. It means you're not paying attention to important areas of your life. Step 2: Defining Your 2025 Themes (Not Goals) Here's where we break from tradition. Instead of writing a list of specific goals, you're going to define 3-4 themes for your year. What's a Theme? A theme is a directional intention that guides decisions without prescribing specific outcomes. Bad goal: "Lose 20 pounds by June"Good theme: "Year of Physical Vitality" Bad goal: "Make $100K from side hustle"Good theme: "Year of Building in Public" Bad goal: "Read 52 books"Good theme: "Year of Deep Learning" See the difference? Themes are flexible. They allow serendipity. They don't create the pass/fail binary that makes you quit when you miss one arbitrary target. How to Choose Your Themes Look at your annual review. What patterns emerge? What needs attention? Your themes should address: One area that's working (double down) One area that's broken (repair) One area you've been ignoring (explore) Optional: One "wildcard" (experiment) My 2025 Themes (As Examples) Theme 1: "Year of Depth Over Breadth" Why: I realized I was consuming hundreds of articles but retaining nothing. Starting dozens of projects but finishing few. Knowing surface-level information about everything but expert-level knowledge about nothing. What this means: Read fewer books but take notes. Start fewer projects but ship them. Have fewer coffee chats but deeper friendships. Master one new skill instead of dabbling in five. Theme 2: "Year of Physical Foundation" Why: I'm 41 now. My body started sending signals I've been ignoring. Energy is declining. Recovery takes longer. If I don't build the foundation now, my 50s will be rough. What this means: Strength training 3x/week. Walking 10K steps daily. Sleep 7.5 hours minimum. No alcohol Monday-Thursday. Annual full health panel. Theme 3: "Year of Creative Output" Why: I've been consuming and learning for years. Time to create and share. Writing, building, teaching moving from input to output. What this means: Write weekly (like this). Build one significant project. Share my process. Focus on creation over consumption. Theme 4: "Year of Intentional Relationships" Why: I've let friendships atrophy. Prioritized work over people. Been physically present but mentally absent with family. What this means: Monthly dinners with close friends. Weekly date nights with my partner. Phone off during family time. Quality over quantity. Notice: These aren't specific. There's no "lose 15 pounds by April." But they're directional. Every decision throughout the year can be measured against these themes. "Should I accept this speaking gig?" → Does it support "Creative Output"?"Should I go to this networking event?" → Does it support "Depth Over Breadth" and "Intentional Relationships"?"Should I stay up late finishing this project?" → Does it conflict with "Physical Foundation"? Your Turn: Define Your 3-4 Themes Spend 30-60 minutes on this. Write them down. Make them personal. Make them yours, not what sounds good on Instagram. For each theme, write: Why it matters to you (personal motivation) What success looks like (loose definition, not strict metrics) What you're saying no to (equally important) Step 3: The Quarterly System (Not Annual Goals) Annual goals fail because twelve months is too long. Life changes. Priorities shift. The person you are in December is not the person you were in January. Solution: Quarterly planning with annual themes. How It Works Every 90 days (January, April, July, October), you do a mini-planning session: Review last quarter: What worked? What didn't? What changed? Check your themes: Are they still relevant? Do they need adjustment? Set 1-3 quarterly objectives: Specific projects that align with your themes The key difference: You're not locked into January's vision. You're continuously adapting while staying true to your annual direction. Example: My Q1 2025 Plan Theme: Year of Physical FoundationQ1 Objective: Establish workout habit and baseline health metrics Specific actions: Join gym by January 5th Hire trainer for 8 sessions (learning proper form) Schedule full health panel by January 15th Track workouts 3x/week minimum Set up meal prep system Success criteria: By April 1st, working out feels automatic, not forced. I have baseline health data. I know what "good form" feels like. Theme: Year of Depth Over BreadthQ1 Objective: Implement reading and learning system Specific actions: Read 3 books (not 12) but take comprehensive notes Implement Zettelkasten method for knowledge management Review and consolidate all open browser tabs and saved articles Unsubscribe from 80% of newsletters Success criteria: By April 1st, I can explain key concepts from those 3 books in my own words. My notes system is working. Theme: Year of Creative OutputQ1 Objective: Establish writing cadence Specific actions: Write and publish 12 blog posts (one per week) Build email list (even if small) Share process and learnings publicly Success criteria: By April 1st, writing weekly feels natural. I have a small but engaged audience. See how this works? Each quarter advances your annual themes with specific, achievable objectives. But you're not overcommitting for the full year. Your Q1 2025 Objectives For each of your themes, define ONE specific objective for Q1. Not three, not five. One. This is crucial: Undercommit and overdeliver. Most people fail because they commit to fifteen things in January. By February, they've failed at fourteen of them and feel like garbage. Instead, commit to 3-4 things (one per theme). Crush them. Build momentum. Add more in Q2. Step 4: The Weekly Operating System Quarterly planning is great, but life happens weekly. Here's where your operating system runs day-to-day. Sunday Planning Ritual (30 minutes) Every Sunday evening: Review last week: Quick wins? What didn't happen? Why? Check quarterly objectives: Am I on track? Plan next week: Big 3: What are the three most important things? Theme check: Does my calendar align with my themes? Time blocks: When will deep work happen? The Sunday ritual is non-negotiable. This is your weekly reset. Miss this, and the week runs you instead of you running the week. Daily Shutdown Ritual (15 minutes) Every workday ends with this: Brain dump: What's still open? What's nagging you? Tomorrow's Big 3: What must happen tomorrow? Close all tabs: Literally. Don't leave tomorrow's work visible. Physical shutdown: Shut laptop. Put phone on charger in another room. Why this matters: Your brain needs clear signals. "Work over" must be marked. Otherwise, you're always half-working and never fully resting. The Monday Morning Launch (30 minutes) Every Monday: Review Big 3 for the week Time block your week Identify obstacles: What could derail you? Pre-commit: Tell someone what you're doing this week Accountability changes everything. Tell your partner, post on social media, text a friend. Make it real. Step 5: The Anti-Fragile System (Planning for Failure) Here's what nobody tells you: You will fail. You'll miss workouts. You'll break your diet. You'll skip writing sessions. You'll fall off the wagon. The difference between people who succeed and people who quit? How they handle the inevitable failures. The Resilience Protocol When you break your streak, miss a goal, or fall back into old patterns: 1. The 48-Hour Rule You get 48 hours to get back on track. Miss a Monday workout? Hit the gym by Wednesday. Skip your writing session? Write double tomorrow. But if 48 hours pass without correction, you're in a spiral. Catch it early. 2. The Post-Mortem (Not Punishment) When you fail, ask: What happened? (Facts, not feelings) What was the trigger? What could prevent this next time? Bad response: "I'm so undisciplined. I suck."Good response: "I skipped the gym because I scheduled it at 6 AM and I'm not a morning person. Solution: Switch to lunchtime workouts." 3. The 80% Rule If you hit 80% of your weekly goals, that's a win. Not 100%. Not perfection. 80%. Three workouts instead of four? Great week.Two blog posts instead of three? Excellent.Maintained your theme 5 days out of 7? You're crushing it. Perfection is the enemy of consistency. Consistency beats intensity every time. Building Redundancy Your system should have backup plans: Can't go to the gym? Have a home workout routine. Too busy to cook? Have healthy meal delivery as backup. Can't write for 2 hours? Write for 20 minutes. The goal is never to have a "zero day." Even a 10% effort day keeps momentum alive. Step 6: The Mid-Year Audit (Essential) Mark July 1st on your calendar right now. This is your mid-year checkpoint. By July, six months will have passed. You'll have completed Q1 and Q2. Time for a comprehensive review: What's working? Which themes are alive and thriving? Which quarterly objectives succeeded? What systems are humming along? What's not working? Which themes feel forced or irrelevant? Which objectives were abandoned? What's draining energy? What needs to change? Do your themes need revision? Do your systems need updating? Are you solving the right problems? Critical permission slip: You can change your themes mid-year. If "Year of Creative Output" isn't resonating, and you're energized by something else, CHANGE IT. This isn't failure it's learning and adapting. Rigidity kills systems. Flexibility keeps them alive. The Meta-System: Energy Management Over Time Management Here's the paradigm shift that changed everything for me: Stop managing time. Start managing energy. You have roughly the same number of hours as everyone else. The difference isn't time, it's energy. The Four Energy Buckets Your energy flows through four channels: 1. Physical Energy Sleep quality and quantity Nutrition and hydration Exercise and movement Physical health 2. Mental Energy Focus and attention Cognitive load Learning and processing Decision fatigue 3. Emotional Energy Relationships and connection Stress and anxiety Joy and fulfillment Emotional regulation 4. Spiritual Energy (or "Meaning," if spiritual feels too woo-woo) Purpose and direction Values alignment Contribution and impact Growth and becoming Most productivity advice only addresses mental energy (focus, time blocking, etc.). But if your physical energy is shot because you sleep 5 hours, your mental energy suffers. If your emotional energy is drained by toxic relationships, nothing else works. Your Energy Audit For each bucket, rate yourself 1-10 right now: Physical: ___/10 Mental: ___/10 Emotional: ___/10 Spiritual: ___/10 Now, here's the key insight: Your weakest bucket determines your capacity. You can't think clearly when exhausted. You can't sustain effort without meaning. You can't focus when emotionally distraught. Your 2025 system must address all four buckets. Not just productivity hacks. The 2025 Calendar: Strategic Time Blocking Now we get tactical. Open your calendar right now. We're going to structure your year. Annual Anchors Block these dates now: Quarterly Planning Weeks: Jan 1-5: Q1 planning Apr 1-5: Q2 planning Jul 1-5: Q3 planning + mid-year audit Oct 1-5: Q4 planning Vacation/Recovery Weeks (minimum 3 per year): Pick them now Block them Protect them like your life depends on it (it does) Theme Review Days (one per month): Last Sunday of each month 2-hour deep review of your themes Adjust, refine, recommit Weekly Template Don't leave your week to chance. Create a template: Monday: Planning and admin (catching up from weekend)Tuesday-Thursday: Deep work blocks (your most important work)Friday: Finishing, reviewing, communicatingSaturday: Personal projects and recoverySunday: Planning and preparation for next week Within each day, block: 9-12 PM: Deep work (no meetings, no email) 12-1 PM: Lunch and movement 1-3 PM: Meetings and collaboration 3-4 PM: Admin and communication 4-5 PM: Wrap-up and planning This is a template, not a prison. Adjust for your reality. But having structure prevents decision fatigue. The "Meeting Budget" Here's a radical idea: You get 10 hours of meetings per week. Maximum. More than that, and you have no time for actual work. Track it. If you're consistently over, you have a boundary problem, not a time problem. How to enforce: Block "deep work" time on calendar (mark as busy) Default to 25-minute meetings (not 30) Decline meetings with no clear agenda Ask: "Could this be an email?" The Accountability System Systems without accountability drift. Here's how to stay on track: The Accountability Partner Find one person friend, colleague, coach who will check in monthly. Not to judge, but to witness. Share your themes. Share your quarterly objectives. Report progress. The magic: Knowing someone will ask about it makes you do it. The Public Commitment This feels scary, but it works: Share your themes publicly. Twitter thread. LinkedIn post. Blog post. Email to friends. Doesn't matter where. Make it real by making it visible. Why this works: Social pressure is powerful. You're not just letting yourself down you're letting others down. That external accountability fills gaps when internal motivation fails. The Weekly Score Every Sunday, rate yourself 1-10 on each theme. Physical Foundation: 7/10 (worked out 3x, but sleep was poor) Depth Over Breadth: 8/10 (finished one book, great notes) Creative Output: 5/10 (published one post, skipped the other) Intentional Relationships: 6/10 (had great dinner with friends, but was on phone during family time) Track this in a simple spreadsheet. Over time, patterns emerge. You'll see what's working and what needs attention. The Common Pitfalls (And How to Avoid Them) Let me save you from the mistakes I've made: Pitfall 1: The Shiny Object Syndrome Mid-February, you'll see someone crushing it at something you're not doing. You'll be tempted to abandon your themes and chase theirs. The antidote: Your themes exist for a reason. Trust past-you who set them during clear-headed reflection. Wait until July 1st to make major changes. Pitfall 2: The Complexity Trap You'll be tempted to add more more goals, more tracking, more systems. The antidote: Complexity kills execution. Every addition should replace something, not just add to the pile. Simplicity scales. Pitfall 3: The All-or-Nothing Thinking One bad week and you'll think, "Well, I've already blown it. Might as well quit." The antidote: Remember the 48-hour rule. Remember the 80% rule. Progress, not perfection. Pitfall 4: The Isolation Trap You'll try to do this alone. It won't work. The antidote: Share your journey. Find your people. Community isn't optional it's essential. The Integration: Bringing It All Together Let's connect all the pieces: Your Annual Themes → The "why" and directionYour Quarterly Objectives → The "what" you're working onYour Weekly Planning → The "when" and "how"Your Daily Rituals → The execution and consistencyYour Energy Management → The fuel for everythingYour Accountability → The tracking and adjustment This isn't seven separate systems. It's one integrated operating system for your life. Each level supports the next. Daily rituals feed weekly planning. Weekly planning advances quarterly objectives. Quarterly objectives manifest annual themes. And your themes? They're expressions of who you're becoming. The Ultimate Question: Who Do You Want to Be in December 2025? Forget the goals for a moment. Close your eyes and imagine: It's December 31st, 2025. You're reflecting on the year. What would make you proud? Not what would impress others. Not what would look good on LinkedIn. What would make YOU proud? How do you feel in your body? What have you created? What relationships have deepened? What have you learned? What have you contributed? How have you grown? Write this vision down. In detail. As if it's already happened. This is your North Star. Your themes and systems exist to move you toward this vision. The 7-Day Launch Plan Okay, enough theory. Here's your action plan for the next seven days: December 23-26: Reflection Phase Complete your annual review (5 questions + quantitative audit) No planning yet, just honest reflection Let insights simmer December 27-28: Design Phase Define your 3-4 themes for 2025 Write why they matter Share them with one person December 29-30: Planning Phase Set Q1 objectives (one per theme) Plan your January calendar Set up your weekly template December 31: System Setup Install any needed apps or tools Set up tracking systems (simple spreadsheet is fine) Write your first weekly plan for Jan 1-5 January 1-5: Launch Week Execute your first week following the system Adjust what doesn't feel right Celebrate small wins January 6: First weekly review. How'd it go? What needs tweaking? The Permission Slips You Need Before we close, I want to give you explicit permission for things that feel taboo: Permission to start small. Three themes is enough. One objective per theme is enough. You don't need a complicated system. Permission to change your mind. If something isn't working, change it. Adaptation is strength, not weakness. Permission to say no. To opportunities, to requests, to things that don't serve your themes. "No" is a complete sentence. Permission to rest. Rest isn't failure. Rest is how you sustain effort. Recovery is when growth happens. Permission to be imperfect. You'll mess up. You'll have bad days. You'll fall short. You're human. Keep going anyway. Permission to want different things than others. Your themes don't have to make sense to anyone but you. Final Words: The Long Game Revisited I started this series talking about my 2014 article on procrastination. That was eleven years ago. Eleven years. You know what I've learned in that time? The people who win aren't the most talented. They're the most consistent. They're the ones who keep showing up. Who adapt when things change. Who build systems that work for them, not against them. Most people will spend 2025 the same way they spent 2024 hoping for change but not changing their systems. You're different. You're here, on December 22nd, doing the hard work of building your foundation and designing your year. That already puts you in the top 5%. But here's the real test: Will you do the work in June when motivation has faded? Will you do the work in September when life gets chaotic? Will you do the work in November when you're tired and want to coast into the holidays? The answer depends on your system. Not your motivation. Not your willpower. Your system. Build the system now. Trust the system later. Your Next Steps If you haven't read Part 1: Go read it now. Foundation first. Complete your annual review: Block time this week. No excuses. Define your themes: Write them down. Share them with someone. Join the conversation: Comment below with your themes. Let's support each other. Accountability starts here. Set a reminder: July 1st, 2025. "Mid-year audit." You'll thank me later. The Commitment I'm doing this with you. My themes are public (see above). I'll be sharing updates throughout the year wins, failures, adjustments. Follow along on my blog. Hold me accountable. Let me hold you accountable. Because here's the truth: We're all making this up as we go. Nobody has it figured out. But we figure it out faster together. 2025 isn't about being perfect. It's about being intentional. It's about designing a life instead of defaulting to one. It's about becoming the person you want to be, one small decision at a time, one week at a time, one quarter at a time. The year starts now. Not January 1st. Now. Let's make it count. This is Part 2 of our Pre-New Year Series. Part 1, "Stop Procrastinating in 2025: Building Your Foundation Before New Year's Resolutions," covers the essential foundation work. Looking back at my 2014 journey of fighting procrastination, I realize that beating distraction was only step one. Building a life that matters; that's the real work. That's what this series is about. What are your themes for 2025? Share them below public commitment is powerful. Let's build our Life Operating Systems together. P.S. - If this resonated with you, share it with someone who needs to read it. Systems work better when we build them together. --- # Stop Procrastinating in 2025: Part 1 – Building Your Foundation Before New Year’s Resolutions URL: https://www.ivanturkovic.com/2025/12/15/stop-procrastinating-in-2025-part-1-building-your-foundation-before-new-years-resolutions/ Published: 2025-12-15 Topics: Career & Leadership, Startups & Business Why December Is Actually the Best Time to Stop Procrastinating As we approach 2025, most people are preparing their New Year's resolutions. But here's the uncomfortable truth: 91% of New Year's resolutions fail by February. Why? Because people try to build new habits on top of broken systems. Before you write that ambitious list of goals for 2025, let's address the elephant in the room: procrastination. You can't build a skyscraper on quicksand, and you can't achieve your dreams while drowning in digital distractions. Back in 2014, I wrote about my battle with procrastination, and the core principles still hold true. But the battlefield has changed dramatically. We're no longer just fighting Facebook and Twitter we're battling algorithmic dopamine machines designed by the smartest engineers in the world to keep us scrolling. This is Part 1 of our pre-New Year series: Building your foundation. Consider this your December homework before the calendar flips to January 1st. The 2025 Procrastination Landscape: What's Different Let me be honest with you. If I could travel back to 2014 and show myself what 2025 looks like, I'd be horrified. Here's what's changed: The New Threats TikTok and Short-Form Video: In 2014, the worst distraction was clicking through articles. Now? You can lose 3 hours watching 15-second videos without even realizing it. The average user spends 95 minutes per day on TikTok alone. AI-Powered Feeds: Every social platform now uses machine learning to show you exactly what keeps you engaged. They know you better than you know yourself. Facebook's "dumb" chronological feed from 2014 looks quaint compared to today's psychologically optimized infinite scroll. Notification Overload: In 2014, we had email and maybe a dozen apps. Now? The average person receives 46 push notifications per day. Each one is a tiny interruption, fragmenting your attention into useless shards. The "Always-On" Culture: Remote work blurred the lines between work and life. Slack, Teams, Discord these tools keep us perpetually connected and perpetually distracted. Doomscrolling: A term that didn't even exist in 2014. The compulsive need to consume negative news has become its own form of procrastination paralysis. But There's Good News The tools to fight back have also evolved. We now understand the neuroscience of habit formation better. We have sophisticated productivity tools. And most importantly, there's a growing cultural awareness that our relationship with technology needs to change. The Core Principles (Still Valid From 2014) Before we dive into 2025-specific tactics, let's revisit what worked a decade ago because these fundamentals haven't changed: 1. Environmental Control Beats Willpower You can't resist temptation if you're constantly surrounded by it. In 2014, I recommended SelfControl for Mac to block distracting websites. This principle remains crucial: remove the temptation rather than fighting it. The human brain hasn't evolved in 10 years. We still have finite willpower. Every time you resist checking Twitter, you drain your mental battery. Save that energy for things that matter. 2. Information Diets Are Essential In 2014, I struggled with 200+ unread RSS items per day. Today, that number looks almost manageable. The modern professional can easily face 500+ items daily across multiple platforms. The solution then and now: curate ruthlessly. Not all information is equal. Most isn't worth your time. 3. Batch Your Distractions Rather than fighting your need for updates, schedule it. In 2014, I suggested reading news only on mobile during breaks. This batching principle is even more critical now. 4. Turn Off Notifications This advice from 2014 is perhaps even more important today. Your phone should serve you, not interrupt you. The Modern Arsenal: 2025 Tools and Techniques Now let's upgrade your toolkit with what we've learned in the past decade: 1. The "One Second Habit" Technique New research from behavior scientists shows that the key to breaking phone addiction isn't blocking apps it's adding friction. Here's what works: Put your phone in grayscale mode: Color activates the reward centers in your brain. Grayscale makes everything less appealing. (iPhone: Settings > Accessibility > Display > Color Filters | Android: Settings > Digital Wellbeing > Bedtime mode) Remove all social media from your home screen: Make yourself search for apps manually. That 3-second delay is often enough to break the automatic reach. Use iOS Focus Modes or Android's Digital Wellbeing: These weren't available in 2014. They automatically filter notifications based on context (work, personal, sleep). Enable Screen Time/Digital Wellbeing weekly reports: Awareness is the first step. You can't improve what you don't measure. 2. The AI-Powered Content Filter Remember Feedly from 2014? It's still great, but now we have AI-enhanced alternatives: Readwise Reader: Uses AI to summarize articles and extract key points. You can consume 10x more content in the same time or more likely, realize 90% isn't worth reading at all. Matter or Omnivore: Modern read-it-later apps with built-in text-to-speech. Listen to articles while commuting, walking, or doing chores. Claude, ChatGPT, or other AI tools: Copy long articles and ask for summaries. Get the key points in 30 seconds instead of reading for 15 minutes. (But use this wisely some things deserve full attention.) 3. The Modern Website Blocker SelfControl is still excellent for Mac, but the ecosystem has expanded: Freedom (Cross-platform): Works on Mac, Windows, iOS, and Android simultaneously. Block apps and websites across all devices. Opal (iOS): Specifically designed for iPhone addiction. Uses AI to learn your patterns and intervene at weak moments. Cold Turkey (Windows/Mac): More aggressive than SelfControl. Can't be bypassed even with a restart. LeechBlock NG (Browser extension): Free alternative that works across Chrome, Firefox, and Edge. Pro tip for 2025: Block at the router level using Pi-hole or NextDNS. This blocks distracting sites for everyone on your network during work hours. Extreme? Yes. Effective? Absolutely. 4. The "Monk Mode" Protocol This is a new concept gaining traction in high-performance circles: Pick one day per week (or one week per month) where you: Delete all social media apps from your phone Turn off email notifications Set an auto-responder saying you're in deep work mode Work only on your most important project Sounds extreme, but here's what happens: You remember what deep focus feels like. You realize how much of your "work" was just performance anxiety about appearing busy. 5. The Pomodoro Technique, Upgraded The classic Pomodoro (25 minutes work, 5 minutes break) works, but 2025 research suggests customization based on your task: Deep creative work: 90-minute blocks with 20-minute breaks (matching your ultradian rhythm) Cognitively demanding tasks: 50-minute blocks with 10-minute breaks Routine tasks: Classic 25/5 works fine Apps like Forest, Focus@Will, or Brain.fm can help. Forest particularly gamifies focus by growing a virtual tree while you work kill the session, kill the tree. Surprisingly effective psychological trick. 6. The "Implementation Intention" Framework This is neuroscience-backed goal setting. Instead of "I won't check social media," you say: "When I feel the urge to check social media, I will instead [specific alternative action]." Examples: "When I feel the urge to check Instagram, I will do 10 push-ups." "When I feel the urge to check news, I will drink a glass of water." "When I open my browser, I will immediately go to my task list, not my email." This works because your brain craves completion. You're not denying yourself you're replacing the behavior. The Science Update: What We've Learned Since 2014 Neuroscience has exploded in the past decade. Here's what matters: Dopamine Detox Is Real (But Misunderstood) You've probably heard about "dopamine detox." The science: Your brain has dopamine receptors. Constant stimulation (social media, junk food, etc.) down-regulates these receptors. You need more stimulation to feel the same reward. The solution: Period of lower stimulation allows receptors to up-regulate. You become more sensitive to pleasure from ordinary activities (work, conversation, nature). Practical application: One day per week, avoid all digital entertainment. No phone (except calls), no TV, no music with lyrics, no internet browsing. Read physical books, go for walks, have face-to-face conversations. Boring? At first. Transformative? Absolutely. The Attention Economy Is Designed to Win Companies literally hire "attention engineers" (reformed slot machine designers) to make apps addictive. This isn't conspiracy theory it's business model. Understanding this isn't defeatist. It's empowering. You're not weak-willed. You're fighting billion-dollar behavioral engineering. Your response: Asymmetric warfare. You can't beat them with willpower. You beat them by not showing up to the battle. Delete the apps. Context Switching Is Killing Your IQ Research from Microsoft and UC Irvine shows each interruption costs you up to 23 minutes of recovery time. Check your phone every 15 minutes? You're never actually working. Even worse: Regular context switching literally makes you stupider. A University of London study found constant email/message checking reduces IQ by 10 points more than smoking marijuana. The fix: Block time. Batch communication. Check email/messages at 10 AM, 2 PM, and 4 PM. Not before, not after, not in between. Your December Action Plan: Pre-New Year Foundation Forget January 1st for now. Let's build your foundation this month: Week 1 (Dec 16-22): Awareness Install a screen time tracker on all devices Log everything you do for one week in a simple notebook Don't try to change anything just observe Most people are shocked by the results. You probably think you work 6 hours a day. The data will show it's closer to 2.5 hours of actual focused work. Week 2 (Dec 23-29): Experimentation Try each blocking tool mentioned above for 2 days Experiment with grayscale mode Test different Pomodoro intervals Find what actually works for you (not what sounds good) Week 3 (Dec 30 - Jan 5): Implementation Choose your core tools (don't overcomplicate) Set up your blocking schedule Create your "implementation intentions" Start January 1st with a working system, not wishful thinking Week 4 (Jan 6-12): Refinement Review what worked and what didn't Adjust your system Remove what isn't helping Double down on what is The Uncomfortable Truth About Habits Here's what I learned in 10 years since my original article: You don't actually want to stop procrastinating. I know, I know. "But Ivan, I DO want to stop!" No, you want to have stopped. You want the results. You don't want the daily discomfort of choosing hard work over easy distraction. This is actually liberating. Stop beating yourself up. Procrastination is the path of least resistance. Your brain is working exactly as designed conserving energy, seeking pleasure, avoiding discomfort. Success isn't about wanting to change. It's about building systems that make the right choice the path of least resistance. Make It Easy to Start, Hard to Stop The best habit changes I've made: Deleted social media apps (easy to stay off, hard to reinstall) Left my phone in another room while working (easy to focus, hard to grab phone) Set up automatic blockers (easy to work, hard to access distractions) Notice the pattern? I'm not relying on willpower. I'm relying on friction. Special Considerations for 2025 The AI Work Revolution AI tools like Claude, ChatGPT, and others are transforming work. This creates a new procrastination trap: research paralysis. You can spend hours reading about AI capabilities instead of using them. You can spend days optimizing your prompts instead of shipping your project. The antidote: Time-box your AI exploration. Give yourself 1 hour per week to explore new AI tools. Outside that hour? Use what you already know. The Async Work Trap Remote work is wonderful, but "async communication" often means "always-on communication." Slack at 11 PM. Emails on Sunday. The procrastination pendulum swings both ways you either work all the time or feel guilty all the time. The solution: Define your hours. Communicate them clearly. Use auto-responders. Protect your deep work time like you'd protect a meeting with your biggest client. The Content Creation Pressure Everyone's a creator now. YouTube, TikTok, LinkedIn, Medium, Twitter/X. The pressure to constantly produce content becomes its own procrastination you're busy being busy, but not actually doing your real work. Reality check: You don't need to be everywhere. You don't need to post daily. Quality over quantity isn't just advice it's the only sustainable path. The Meta-Lesson: Progress Over Perfection Here's what I wish I'd known in 2014: You'll never completely stop procrastinating. I still waste time. I still have bad days. The difference? Bad days are now exceptions, not the rule. I have systems that catch me when I fall. The goal isn't perfection. It's progress. It's having more good days than bad days. It's building momentum that carries you forward even when motivation fails. Preparing for Part 2: The New Year Strategy This article focused on stopping the bleeding fixing your procrastination patterns before January 1st. Part 2 (coming next week) will tackle the harder question: Once you've reclaimed your time and attention, what should you actually do with it? We'll cover: Setting goals that actually work (hint: SMART goals are outdated) Building a life system, not just productivity hacks Planning for inevitable setbacks Creating your 2025 roadmap But here's the key: Part 2 only works if you do the work from Part 1. You can't plan a successful year while still losing 4 hours a day to digital distraction. Your Call to Action Don't just read this and move on. That's another form of procrastination consuming information instead of implementing it. Right now, do these three things: Put your phone in grayscale mode (do it now, I'll wait) Download one blocking app and schedule your first focused work session for tomorrow Write down your "when-then" implementation intentions on a sticky note by your computer That's it. Don't try to implement everything at once. Three small actions, done today, beat ten grand plans that live in your head. Final Thoughts: The Long Game It's been 10 years since my original article. A decade. That seems like a long time, but here's what I've learned: The years will pass regardless. You can enter 2035 having spent 10 years as a distracted, procrastination-prone person who was always about to turn things around. Or you can spend those 10 years as someone who ships, who focuses, who does the work. The compounding effect is staggering. Not in motivation (that fades) but in systems. Every good system you build this year makes next year easier. Every habit you cement now pays dividends for life. December 2024 is your foundation. January 2025 is your launch. But the real magic? It's the December 2034 version of yourself, looking back and barely remembering what chronic procrastination felt like. That future starts now. Not January 1st. Now. Let's build something worth building. This is Part 1 of our Pre-New Year Series. Part 2, "From Intentions to Impact: Your 2025 Strategy Guide," publishes on December 22nd. Looking back at my journey, my original 2014 article captured the fundamentals that still work today. But the battlefield has changed. The principles remain the same, but the tactics must evolve. This series bridges that gap. What's your biggest procrastination trigger in 2025? Drop a comment below let's figure this out together. --- # The Corporate Culture Charade Part 2: How AI Is Killing What Little Culture We Had Left URL: https://www.ivanturkovic.com/2025/12/13/the-corporate-culture-charade-part-2-how-ai-is-killing-what-little-culture-we-had-left/ Published: 2025-12-13 Topics: Career & Leadership While executives blame remote work for destroying company culture, they're missing the real culprit: AI-generated content is creating a closed loop of meaningless communication where everyone is reading summaries of summaries, and nobody is thinking anymore. I need to start with a confession: I've used AI to write emails. I've used it to summarize meeting notes. I've watched colleagues use it to generate reports that get sent to other colleagues who use AI to summarize those reports. And I've realized we're all participating in the creation of a vast corporate content ouroboros a snake eating its own tail, except the snake is made of statistical predictions and nobody notices there's no actual substance being consumed. The Real Problem: It's Not Remote Work, It's Synthetic Work Leadership loves to blame remote work for the decline in culture. They point to the loss of hallway conversations, the absence of spontaneous collaboration, the way Zoom calls feel transactional. But they're looking in entirely the wrong direction. The actual culture killer isn't physical distance it's cognitive distance. It's the growing gap between the work people pretend to do and the work that actually matters. And AI is widening that gap exponentially. Here's what's actually happening in companies right now: Sarah uses AI to write a quarterly report. The report sounds professional, hits all the expected notes, includes data visualizations that look impressive. She sends it to her manager. Her manager, who receives dozens of such reports, uses AI to summarize it into three bullet points. These bullets go into his own AI-generated executive summary. That summary gets presented in a meeting where attendees use AI to generate their meeting notes, which get distributed to people who weren't there, who use AI to extract action items, which go into project management tools where AI generates status updates. At no point did anyone actually think deeply about anything. At no point did genuine human judgment get applied. At no point was there real synthesis, real analysis, real understanding. It's all just AI talking to AI, with humans serving as the medium of exchange. The Degradation Cycle: Summaries of Summaries We're witnessing something unprecedented in corporate history: the rapid degradation of information quality through successive AI transformations. It's like making a photocopy of a photocopy of a photocopy each iteration loses fidelity, introduces artifacts, strips away nuance. Generation 1: AI-Generated Content Someone uses AI to write initial content a proposal, a report, an analysis. The AI is trained on existing corporate documents, so it produces something that sounds right, uses the appropriate jargon, follows expected formats. But it lacks genuine insight. It can't make intuitive leaps. It doesn't understand context the way humans do. This content is polished but shallow. It says what's expected without saying anything surprising or truly valuable. It's the corporate equivalent of empty calories. Generation 2: AI-Summarized Content The recipient of this content doesn't read it carefully who has time? so they use AI to summarize it. The summary extracts what the AI thinks are the key points, which may or may not be what a human would consider important. Nuance disappears. Qualifications get stripped away. Uncertainty becomes certainty. Now we're working with a summary of shallow content. We've gone from empty calories to nutritional vapor. Generation 3: AI-Synthesized Responses Based on this summary, someone uses AI to generate a response or create derivative content. Maybe it's action items. Maybe it's a presentation. Maybe it's input for another report. The AI is now working with twice-distilled information, generating content based on a summary of shallow original content. We're three generations removed from human thought, and each generation has introduced errors, lost context, and stripped away meaning. Generation N: The Content Wasteland Now imagine this cycle repeating dozens, hundreds, thousands of times across an organization. Every document, every email, every report going through this process. The institutional knowledge base becomes increasingly detached from reality. Decisions get made based on information that has been transformed so many times that it bears little resemblance to the original insights, data, or understanding that prompted the first document. We're creating a corporate Tower of Babel where everyone is technically communicating but nobody is actually understanding each other because all the communication is mediated through AI that's optimizing for surface-level coherence rather than deep meaning. The Quality Collapse: When Everyone Stops Thinking The most insidious effect of this AI-mediated communication isn't just the degradation of information quality it's the atrophy of human judgment and thinking. When you know you can generate a report with AI, why spend hours thinking through the problem deeply? When you know your manager will just summarize it with AI anyway, why craft your arguments carefully? When everyone is using AI to process information, why develop your own analytical capabilities? We're in a race to the bottom of cognitive effort. The tools that were supposed to free us from drudgery are instead freeing us from thinking. And companies are celebrating this because it looks like efficiency. The Illusion of Productivity Look at the metrics: More reports produced! Faster response times! Higher email volume! More documentation! By every quantitative measure, people are more productive than ever. But productivity of what? We're producing more content, yes. But is any of it meaningful? Is any of it moving the organization forward? Is any of it based on genuine analysis and insight? We've optimized for the appearance of work rather than the substance of work. We've created systems that reward volume over value, speed over thoughtfulness, and polish over profundity. The Death of Institutional Knowledge Institutional knowledge the deep understanding of how things really work, why decisions were made, what was tried and failed, what context matters is built through human experience, interpretation, and communication. It's the stories people tell. It's the judgment that comes from seeing patterns over time. It's the wisdom that emerges from collective experience. AI-mediated communication short-circuits this entire process. The stories don't get told because they get summarized away. The judgment doesn't develop because decisions are based on statistical predictions rather than experience. The wisdom doesn't accumulate because nobody is actually thinking deeply about what's happening. In 10 years, companies are going to find themselves with senior people who can't explain why the organization does what it does, because they've spent their entire careers processing AI-generated content rather than building genuine understanding. How This Actually Kills Culture (Unlike Remote Work) Remember all that talk about culture dying because of remote work? About the loss of spontaneous moments and shared experiences? It was always a red herring. Here's what actually kills culture: Loss of Authentic Voice When everyone's writing is AI-generated, there's no personality. No individual voice. No quirks or humor or unique ways of thinking. Everything sounds the same professional, polished, and utterly generic. Culture is carried in how people communicate. It's in the shared language, the inside jokes, the particular ways of framing problems. When AI mediates all communication, that distinctive voice disappears. Everyone starts sounding like a corporate chatbot. Erosion of Trust Trust requires authentic communication. You need to believe that what someone is saying represents their actual thinking, their real analysis, their genuine perspective. But when you know (or suspect) that the email you just received was AI-generated, and that the person didn't even read your last AI-generated message but just had AI summarize it, what basis is there for trust? You're not actually communicating with each other you're playing an elaborate game of telephone with AI as the intermediary. Culture requires trust. Trust requires authentic communication. AI-mediated communication is inherently inauthentic. The logic is inescapable. Disappearance of Shared Understanding Strong cultures have shared mental models common ways of understanding problems, shared frameworks for making decisions, collective intuitions about what matters. These shared models develop through repeated, meaningful interaction. Through debates where people articulate their thinking. Through collaboration where different perspectives get synthesized. Through mistakes where everyone learns together what not to do. AI-mediated communication prevents this shared understanding from developing. Everyone is operating based on their own AI's interpretation of other people's AI-generated content. There's no genuine meeting of minds, no real synthesis, no collective learning. The Feedback Loop of Mediocrity Here's where things get truly dystopian: AI models are trained on human-generated content. But as more and more of the internet and corporate communication becomes AI-generated, future AI models will be trained increasingly on AI-generated content. This creates a feedback loop. AI generates content that sounds like previous content. That content gets used to train new AI models. Those models generate content that's even more derivative. Which gets used to train the next generation. And so on. Researchers call this "model collapse" the progressive degradation of AI capabilities when models are trained on AI-generated data. But we're also experiencing a kind of cultural and cognitive collapse in organizations. As AI-generated content dominates, the baseline of what constitutes acceptable thinking and communication keeps lowering. The standard becomes: "It sounds professional." Not "It's insightful." Not "It's accurate." Not "It advances our understanding." Just: "It sounds like what a corporate document should sound like." And because everyone's using the same AI tools, trained on the same data, optimizing for the same surface-level qualities, corporate communication becomes increasingly homogeneous and increasingly disconnected from actual thinking. The Missing Diagnosis: Why Leadership Gets This Wrong So why do executives blame remote work instead of recognizing the AI problem? A few reasons: Remote work is visible. You can see that people aren't in the office. You can measure it. You can mandate change. The degradation of thinking and communication quality is much harder to see, especially when it's happening gradually and everyone's doing it. Leaders use AI too. The CEO writing the memo about culture is probably using AI to draft it. The executives advocating for return-to-office are using AI to summarize reports. They're participating in the same system, so they can't see it clearly. The metrics look good. More content produced, faster turnaround times, higher volume of communication by quantitative measures, everything is improving. The qualitative collapse is invisible to metrics-driven management. It challenges the narrative of progress. Companies have invested heavily in AI tools. They've sold shareholders on the productivity gains. They've trained employees on these systems. Admitting that AI is degrading the quality of work and culture would mean admitting they've been moving in the wrong direction. What This Means For The Future We're at an inflection point. The next few years will determine whether companies become AI-mediated content factories where humans serve as simple pass-through nodes, or whether we figure out how to use these tools while preserving genuine human thinking and communication. The latter requires recognizing some uncomfortable truths: Volume Is Not Value Stop measuring productivity by how much content gets produced. Start measuring by the quality of decisions made, problems solved, and understanding developed. This means fewer reports, fewer emails, fewer meetings but better ones. Thinking Is Work Deep analysis, genuine synthesis, original insight these take time. They can't be rushed or automated. Organizations need to create space for actual thinking, which means accepting that people can't be constantly responsive, always producing, perpetually communicating. Authentic Communication Matters More Than Ever In a world of AI-generated content, authentic human communication becomes more valuable, not less. The companies that figure out how to preserve and prioritize genuine human interaction whether remote or in-person will have a massive advantage. Tools Should Augment, Not Replace AI should help people think better, not think less. It should handle truly routine tasks so people can focus on higher-order thinking. But when AI starts doing the thinking itself, and people just route the AI's output to other people's AI, we've crossed a line. The Real Culture Crisis So let's return to where we started: the hand-wringing about culture, the blame placed on remote work, the calls for return-to-office to restore the magic. It's all missing the point. The crisis isn't about physical proximity. It's about cognitive proximity. It's about whether people are actually thinking together, communicating authentically, building shared understanding. You can have all the hallway conversations you want, but if everyone's using AI to write their thoughts and AI to process everyone else's thoughts, there's no genuine meeting of minds happening. You can mandate office presence, but if all the work is mediated through AI, you're just moving the AI-content-generation machines into the same building. The real culture crisis is this: We're building organizations where thinking is optional, where communication is performative, where understanding is assumed but never achieved. We're creating elaborate theater where everyone pretends to work, produces content that looks like work, and processes other people's work-looking content, but nobody is actually solving hard problems or developing genuine insights. And we're blaming remote work for the emptiness we feel, when the real culprit is staring at us from our text editors and email clients and document generators. The future of work isn't about where we work. It's about whether we work at all, or whether we've outsourced thinking to machines that can't actually think, leaving us as mere moderators of a conversation between algorithms. That's the conversation we should be having about culture. Not whether people are in the office, but whether anyone is actually thinking anymore. A Final Provocation I'll leave you with an uncomfortable question: How much of what you've written, read, or processed in the last week was genuinely thought through by a human being? How much was AI-generated? How much was an AI summary of AI-generated content? How many layers removed from actual human thinking are you operating? And if the answer troubles you if you realize you're not sure, if you suspect it's more than you'd like to admit then maybe it's time to stop worrying about where people sit and start worrying about whether anyone's actually thinking. Because that's where culture actually lives: not in offices or video calls, but in the quality of thinking and authenticity of communication that happens between people. And AI, for all its capabilities, is steadily eroding both. --- # Company Culture URL: https://www.ivanturkovic.com/2025/12/11/company-culture/ Published: 2025-12-11 Topics: Career & Leadership There's an elephant in every conference room, and it's time someone pointed it out: corporate culture is mostly performance art for executives who've consumed one too many LinkedIn thought leadership posts. Here's the uncomfortable truth when it comes to company culture, there is really nothing cultural in it by itself at all. I recently came across yet another company memo about culture this one lamenting how remote work is supposedly destroying the magical "energy" and "spontaneous moments" that make a workplace special. The usual suspects were there: hallway conversations, shared excitement, the ineffable sense of belonging that apparently only happens when people share physical space. And I couldn't help but think: who is this actually for? How We Got Here: A Brief History of the Culture Obsession The corporate culture phenomenon didn't emerge from nowhere. To understand why every company now has a Chief Culture Officer and a dedicated culture budget, we need to look back at how this obsession began. The 1980s: Culture as Competitive Advantage The modern corporate culture movement can be traced back to the early 1980s, particularly to two influential books: "In Search of Excellence" (1982) by Tom Peters and Robert Waterman, and "Corporate Cultures" (1982) by Terrence Deal and Allan Kennedy. These books emerged during a time when American companies were losing ground to Japanese competitors, and consultants were desperately searching for explanations. The answer they landed on was culture. Japanese companies supposedly had superior cultures strong shared values, intense loyalty, collective purpose. American companies needed to cultivate similar cultures to compete. Culture became a management tool, something to be engineered and optimized like any other business process. The 1990s-2000s: The Silicon Valley Mythos Then came Silicon Valley, and culture took on a new dimension. Tech companies weren't just selling products they were selling a lifestyle, a vision, a revolution. Culture became part of the brand, both for recruiting and for public image. Google famously codified this with its "Don't be evil" motto and its emphasis on perks: free food, massage rooms, game areas. The message was clear: this isn't just a job, it's a community. Facebook followed with its "move fast and break things" ethos. Apple cultivated an air of creative excellence and secrecy. But here's what really happened: these companies used culture as a substitute for work-life balance. Free dinner wasn't a perk it was an incentive to stay at the office until 9 PM. The ping-pong table wasn't about fun it was about keeping you on campus. The mission-driven culture wasn't about meaning it was about getting you to work 80-hour weeks for below-market equity. The 2010s: Culture Goes Mainstream By the 2010s, every company wanted to be the next Google. Corporate culture became an industry. LinkedIn filled with posts about culture. Business schools taught it. Consultants sold it. HR departments built entire teams around it. Netflix released its famous culture deck in 2009, which has been viewed millions of times. Zappos made headlines by paying people to quit if they didn't fit the culture. HubSpot, Spotify, Airbnb every successful tech company published their culture code, and traditional companies scrambled to copy them. The irony? Most of these companies were successful despite their culture obsession, not because of it. They succeeded because they built products people wanted, captured market opportunities at the right time, or benefited from network effects. The culture was window dressing. The Etymology Betrays the Problem Here's where we need to address the fundamental linguistic sleight of hand. When we talk about "company culture," we're borrowing a word culture that has deep anthropological and sociological meaning. Real culture emerges organically from shared history, values, traditions, and collective experience. It evolves over generations. It's authentic and lived, not designed and mandated. Corporate culture, by contrast, is manufactured. It's decided in boardrooms, written into documents, and rolled out through internal communications. It's not emergent it's imposed. It's not organic it's engineered. When it comes to company culture, there is really nothing cultural in it by itself at all. It's just management strategy dressed up in anthropological language. Real culture the kind anthropologists study comes from the bottom up. Corporate culture comes from the top down. Real culture reflects genuine shared values. Corporate culture reflects what leadership wants people to value. These are fundamentally different things, but we use the same word for both, and that confusion serves the interests of management. The Culture Industry and Its Empty Promises Walk into any modern company and you'll witness the same elaborate theater. Mission statements are plastered across reception walls in carefully chosen fonts. Core values are printed on branded notebooks that nobody opens. There are Slack channels dedicated to "living our culture," all-hands meetings where leadership delivers inspiring speeches about shared purpose, and mandatory workshops on belonging. The culture machinery churns constantly, producing engagement surveys, team-building exercises, and culture decks that get shared on social media as proof of how progressive and human-centered the organization is. But here's the uncomfortable truth that nobody in the C-suite wants to acknowledge: most employees don't actually care about any of this. They care about their work. They care about their paycheck. They care about having autonomy and respect. But the corporate culture apparatus? That's someone else's concern. What People Actually Want From Work Let's cut through the corporate-speak and talk about what employees genuinely value: Fair Compensation Pay people what they're worth. Not what market conditions allow you to get away with. Not what your compensation band dictates. What their actual contribution to the company's success merits. Nothing kills "culture" faster than discovering your colleague doing the same job makes 30% more, or that the company just raised another funding round while salaries remain frozen. You can't pizza-party your way out of pay inequity. You can't build belonging on top of resentment about compensation. Meaningful Work Give people problems worth solving. Grant them genuine autonomy to solve those problems. Provide the resources, tools, and support they need to do excellent work. That's it. That's the formula. People don't need culture programs to feel engaged they need to work on things that matter, with the freedom to approach problems creatively and the support to execute their ideas. Respect for Their Time and Boundaries Stop pretending that mandatory fun is fun. Stop acting like team-building exercises are anything other than unpaid work. Stop scheduling culture activities outside working hours and calling it optional when everyone knows it's not. Respect that people have lives outside of work. That they have families, hobbies, communities, and identities that have nothing to do with the company. The more you try to make the company their primary source of belonging, the more you reveal that you're trying to extract more than you're paying for. Professional Relationships Without Forced Community People are perfectly capable of building genuine connections with colleagues without corporate facilitation. They'll grab coffee with people they like. They'll collaborate effectively with people they respect. They'll form friendships naturally when there's actual compatibility. What they don't need is ice-breaker activities, personality assessments, or trust falls. Organic relationships built through real work will always be stronger than manufactured bonding through company-mandated activities. So Who Actually Benefits From the Culture Obsession? If employees aren't clamoring for more culture initiatives, who is this all for? Leadership Teams Executive leadership gets to feel like visionaries instead of managers. It's far more inspiring to write manifestos about "energy" and "shared purpose" than to fix broken processes, address systemic issues, or have difficult conversations about compensation gaps. Culture talk allows leaders to focus on the intangible and aspirational while avoiding the concrete and measurable. You can't be held accountable for whether the culture feels right, but you absolutely can be held accountable for whether salaries are competitive or processes are efficient. HR Departments Culture initiatives justify expanding HR budgets and headcount. Every new belonging program requires a program manager. Every engagement survey needs analysis. Every culture transformation demands consultants. The output? Endless PowerPoint presentations, internal communications, and reports that measure sentiment instead of solving problems. The culture apparatus becomes self-perpetuating it exists to justify its own existence. Corporate Consultants An entire industry has emerged around selling culture frameworks to companies. The same repackaged ideas get sold again and again, promising transformation while delivering jargon. Every company thinks their culture is unique, so every company pays for a bespoke culture assessment that tells them roughly the same things. The consultants make millions. The culture doesn't actually change. The Remote Work Scapegoat I want to address the specific example that prompted this post the notion that remote work is killing corporate culture by eliminating spontaneous moments and reducing people to "tiles on a screen." This argument is nostalgia disguised as analysis, and it conveniently ignores some inconvenient truths: Those hallway moments were never accessible to everyone. They excluded remote workers, parents who couldn't stay late for after-hours drinks, people with disabilities who found the office environment challenging, and anyone who wasn't part of the dominant in-group. The spontaneous moments were spontaneous for some, but they created systematic exclusion for others. The "shared excitement" happens regardless of location. When a team ships something great, the excitement is genuine whether people are celebrating in a conference room or a group chat. The dopamine hit of solving a hard problem doesn't require physical proximity. Trust and collaboration are built through work, not proximity. You know what builds trust? Delivering on commitments. Communicating clearly. Having each other's backs when things get difficult. Respecting boundaries. None of that requires an office. Forcing people back to offices doesn't create culture. It creates commutes, distractions, and resentment. The push for return-to-office masked as culture concern is often about something else entirely: a desire for visibility and control, real estate investments that need to be justified, or managers who never learned how to lead distributed teams. What Actually Matters If you genuinely want people to care about their work and feel connected to their team, here's what you should focus on: Stop investing in culture programs. Invest in people instead. Channel that budget into competitive compensation, comprehensive benefits, real professional development opportunities, and career growth paths that aren't just theoretical. Stop measuring belonging. Start measuring enablement. Do people have the tools they need? Can they get decisions made efficiently? Are there clear paths for escalation when they're blocked? These are concrete questions with measurable answers. Stop forcing togetherness. Trust teams to self-organize. Some teams will want to meet in person regularly. Others will thrive remotely. Many will want a hybrid approach. Let teams figure out what works for them instead of mandating a one-size-fits-all solution. Stop pretending your company is special. Most companies do roughly similar work. Most face similar challenges. The sooner you accept this, the sooner you can focus on what actually differentiates you: the quality of work you produce, how you treat the people producing it, and whether you deliver value to customers. The Uncomfortable Truth About Corporate Culture Let's say the quiet part out loud: corporate culture isn't really about belonging. It's about control. It's about making people identify with the company so deeply that they'll accept below-market compensation for the privilege of being part of the "mission." It's about creating emotional investment that can be leveraged for longer hours and fewer boundaries. It's about building loyalty that serves the company more than it serves the employee. The culture apparatus exists to make people feel like they're part of something bigger than themselves and then to use that feeling as justification for asking them to sacrifice more. But employees are getting smarter. They see through the posters and the speeches. They recognize when culture talk is cover for avoiding harder conversations about money, equity, and sustainable work practices. They know the difference between genuine care and performative concern. A Final Thought I'm not suggesting that culture doesn't matter at all. Of course it does. How people treat each other matters. Whether there's psychological safety matters. Whether the organization lives up to its stated values matters. But real culture isn't built through programs and initiatives. It's built through a thousand small decisions how you handle a mistake, whether you give credit where it's due, if you protect someone's time off, how you respond when priorities conflict. Real culture is what happens when nobody's performing for leadership. It's the unwritten rules about how work actually gets done. It's whether people feel safe being honest. It's whether the company's actions match its words. You can't manufacture that with team-building exercises and culture decks. You can only create the conditions for it to emerge and then get out of the way. So maybe it's time to stop obsessing over culture as a thing to be built and managed, and start focusing on the fundamentals: doing good work, treating people well, and paying them fairly. The culture will take care of itself. --- # Building a Decentralized Credit Card System Part 2: Solidity Smart Contract Implementation URL: https://www.ivanturkovic.com/2025/12/09/building-a-decentralized-credit-card-system-part-2-solidity-smart-contract-implementation/ Published: 2025-12-09 Topics: Startups & Business In the first part of this series, we explored the conceptual architecture of a blockchain-based credit card system using multi-signature keys and encrypted spending limits. Now, let's dive into the technical implementation with concrete Solidity examples. This post will give you production-ready smart contract code that demonstrates how to build a secure, multi-signature credit card system on Ethereum or any EVM-compatible blockchain. Smart Contract Architecture Overview Our decentralized credit card system consists of five interconnected smart contracts: CreditFacility Contract: Manages the master account and credit line CardManager Contract: Handles individual card issuance and lifecycle SpendingLimits Contract: Enforces encrypted spending rules PaymentProcessor Contract: Executes and settles transactions MultiSigGovernance Contract: Handles high-value transaction approvals Each contract has a specific responsibility, following the principle of separation of concerns. This modular approach makes the system more maintainable, upgradeable, and secure. Note: These contracts are designed for educational and proof-of-concept purposes. Production deployment would require extensive security audits, gas optimization, and integration with off-chain systems. 1. The Credit Facility Contract This contract represents the bank account or credit line, the source of funds controlled by the master key. It implements multi-signature controls to ensure that no single party can unilaterally make critical decisions. Key Features Multi-signature authorization for sensitive operations Credit limit management with approval workflows Real-time tracking of available credit and outstanding balance Support for multiple authorized signers per account Emergency account suspension capabilities The Complete Contract // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @title CreditFacility * @dev Manages the master credit account with multi-sig controls */ contract CreditFacility { struct CreditAccount { uint256 creditLimit; uint256 availableCredit; uint256 outstandingBalance; bool isActive; address[] authorizedSigners; uint256 requiredSignatures; } mapping(address => CreditAccount) public accounts; mapping(address => mapping(bytes32 => uint256)) public transactionApprovals; event CreditAccountCreated(address indexed account, uint256 creditLimit); event CreditLimitUpdated(address indexed account, uint256 newLimit); event CreditUsed(address indexed account, uint256 amount); event CreditRepaid(address indexed account, uint256 amount); event TransactionApproved(address indexed signer, bytes32 transactionHash); modifier onlyAccountOwner(address account) { require(isAuthorizedSigner(account, msg.sender), "Not authorized"); _; } modifier accountActive(address account) { require(accounts[account].isActive, "Account not active"); _; } function createCreditAccount( address accountAddress, uint256 creditLimit, address[] memory signers, uint256 requiredSigs ) external { require(signers.length >= requiredSigs, "Invalid signer configuration"); require(!accounts[accountAddress].isActive, "Account already exists"); accounts[accountAddress] = CreditAccount({ creditLimit: creditLimit, availableCredit: creditLimit, outstandingBalance: 0, isActive: true, authorizedSigners: signers, requiredSignatures: requiredSigs }); emit CreditAccountCreated(accountAddress, creditLimit); } function updateCreditLimit( address account, uint256 newLimit, bytes32 approvalHash ) external onlyAccountOwner(account) accountActive(account) { require(hasRequiredApprovals(account, approvalHash), "Insufficient approvals"); CreditAccount storage creditAccount = accounts[account]; uint256 difference = newLimit > creditAccount.creditLimit ? newLimit - creditAccount.creditLimit : 0; creditAccount.creditLimit = newLimit; creditAccount.availableCredit += difference; emit CreditLimitUpdated(account, newLimit); clearApprovals(account, approvalHash); } function useCredit( address account, uint256 amount ) external accountActive(account) returns (bool) { CreditAccount storage creditAccount = accounts[account]; require(creditAccount.availableCredit >= amount, "Insufficient credit"); creditAccount.availableCredit -= amount; creditAccount.outstandingBalance += amount; emit CreditUsed(account, amount); return true; } function repayCredit( address account, uint256 amount ) external payable accountActive(account) { require(msg.value >= amount, "Insufficient payment"); CreditAccount storage creditAccount = accounts[account]; require(creditAccount.outstandingBalance >= amount, "Overpayment"); creditAccount.outstandingBalance -= amount; creditAccount.availableCredit += amount; emit CreditRepaid(account, amount); } function approveTransaction( address account, bytes32 transactionHash ) external onlyAccountOwner(account) { transactionApprovals[account][transactionHash]++; emit TransactionApproved(msg.sender, transactionHash); } function hasRequiredApprovals( address account, bytes32 transactionHash ) public view returns (bool) { return transactionApprovals[account][transactionHash] >= accounts[account].requiredSignatures; } function isAuthorizedSigner( address account, address signer ) public view returns (bool) { address[] memory signers = accounts[account].authorizedSigners; for (uint i = 0; i < signers.length; i++) { if (signers[i] == signer) return true; } return false; } function clearApprovals(address account, bytes32 transactionHash) internal { delete transactionApprovals[account][transactionHash]; } function getAccountDetails(address account) external view returns ( uint256 creditLimit, uint256 availableCredit, uint256 outstandingBalance, bool isActive ) { CreditAccount storage acc = accounts[account]; return ( acc.creditLimit, acc.availableCredit, acc.outstandingBalance, acc.isActive ); } } Understanding the Code Let's break down the key components of this contract. CreditAccount Structure The CreditAccount struct stores all essential information about a credit account. It tracks the credit limit, available credit, outstanding balance, activation status, and the multi-signature configuration. This structure ensures that all account data is organized and easily accessible. Multi-Signature Security The contract implements a flexible multi-signature system. When creating an account, you specify both the authorized signers and how many signatures are required for critical operations. For example, a business account might have five authorized signers but only require three signatures to approve a credit limit increase. The approveTransaction function allows authorized signers to vote on proposed actions. Once enough approvals are collected, the action can be executed. This prevents any single compromised key from causing damage to the system. Credit Management The useCredit and repayCredit functions handle the core financial operations. When a card makes a purchase (which we'll see in later contracts), it calls useCredit to deduct from the available balance. When a payment is made, repayCredit restores the available credit. Security Feature: Notice how credit operations include checks for account status and available balance. These guards prevent overdrafts and ensure the account is active before any transaction proceeds. 2. The Card Manager Contract While the Credit Facility manages the master account, individual cards need their own management layer. The Card Manager contract handles card issuance, activation, deactivation, and the assignment of spending limits to individual cards. // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface ICreditFacility { function useCredit(address account, uint256 amount) external returns (bool); function accounts(address) external view returns ( uint256 creditLimit, uint256 availableCredit, uint256 outstandingBalance, bool isActive ); } contract CardManager { struct Card { address cardAddress; address linkedAccount; bool isActive; uint256 dailyLimit; uint256 monthlyLimit; uint256 perTransactionLimit; uint256 dailySpent; uint256 monthlySpent; uint256 lastResetDay; uint256 lastResetMonth; string cardholderName; bytes32 cardType; } ICreditFacility public creditFacility; mapping(address => Card) public cards; mapping(address => address[]) public accountCards; mapping(address => bool) public cardExists; event CardIssued( address indexed cardAddress, address indexed account, string cardholderName ); event CardActivated(address indexed cardAddress); event CardDeactivated(address indexed cardAddress); event CardLimitsUpdated(address indexed cardAddress); event SpendingRecorded(address indexed cardAddress, uint256 amount); modifier onlyActiveCard(address cardAddress) { require(cards[cardAddress].isActive, "Card not active"); _; } modifier cardOwner(address cardAddress) { require(msg.sender == cardAddress, "Not card owner"); _; } constructor(address _creditFacility) { creditFacility = ICreditFacility(_creditFacility); } function issueCard( address cardAddress, address account, string memory cardholderName, uint256 dailyLimit, uint256 monthlyLimit, uint256 perTransactionLimit, bytes32 cardType ) external { require(!cardExists[cardAddress], "Card already exists"); cards[cardAddress] = Card({ cardAddress: cardAddress, linkedAccount: account, isActive: true, dailyLimit: dailyLimit, monthlyLimit: monthlyLimit, perTransactionLimit: perTransactionLimit, dailySpent: 0, monthlySpent: 0, lastResetDay: block.timestamp / 1 days, lastResetMonth: getMonthFromTimestamp(block.timestamp), cardholderName: cardholderName, cardType: cardType }); accountCards[account].push(cardAddress); cardExists[cardAddress] = true; emit CardIssued(cardAddress, account, cardholderName); } function activateCard(address cardAddress) external { require(cardExists[cardAddress], "Card does not exist"); cards[cardAddress].isActive = true; emit CardActivated(cardAddress); } function deactivateCard(address cardAddress) external { require(cardExists[cardAddress], "Card does not exist"); cards[cardAddress].isActive = false; emit CardDeactivated(cardAddress); } function updateCardLimits( address cardAddress, uint256 dailyLimit, uint256 monthlyLimit, uint256 perTransactionLimit ) external { require(cardExists[cardAddress], "Card does not exist"); Card storage card = cards[cardAddress]; card.dailyLimit = dailyLimit; card.monthlyLimit = monthlyLimit; card.perTransactionLimit = perTransactionLimit; emit CardLimitsUpdated(cardAddress); } function checkAndRecordSpending( address cardAddress, uint256 amount ) external onlyActiveCard(cardAddress) returns (bool) { Card storage card = cards[cardAddress]; resetSpendingIfNeeded(cardAddress); require(amount <= card.perTransactionLimit, "Exceeds per-transaction limit"); require(card.dailySpent + amount <= card.dailyLimit, "Exceeds daily limit"); require(card.monthlySpent + amount <= card.monthlyLimit, "Exceeds monthly limit"); card.dailySpent += amount; card.monthlySpent += amount; emit SpendingRecorded(cardAddress, amount); return true; } function resetSpendingIfNeeded(address cardAddress) internal { Card storage card = cards[cardAddress]; uint256 currentDay = block.timestamp / 1 days; uint256 currentMonth = getMonthFromTimestamp(block.timestamp); if (currentDay > card.lastResetDay) { card.dailySpent = 0; card.lastResetDay = currentDay; } if (currentMonth > card.lastResetMonth) { card.monthlySpent = 0; card.lastResetMonth = currentMonth; } } function getMonthFromTimestamp(uint256 timestamp) internal pure returns (uint256) { return timestamp / 30 days; } function getCardDetails(address cardAddress) external view returns ( address linkedAccount, bool isActive, uint256 dailyLimit, uint256 monthlyLimit, uint256 perTransactionLimit, uint256 dailySpent, uint256 monthlySpent, string memory cardholderName ) { Card storage card = cards[cardAddress]; return ( card.linkedAccount, card.isActive, card.dailyLimit, card.monthlyLimit, card.perTransactionLimit, card.dailySpent, card.monthlySpent, card.cardholderName ); } function getAccountCards(address account) external view returns (address[] memory) { return accountCards[account]; } function getRemainingDailyLimit(address cardAddress) external view returns (uint256) { Card storage card = cards[cardAddress]; if (card.dailySpent >= card.dailyLimit) return 0; return card.dailyLimit - card.dailySpent; } function getRemainingMonthlyLimit(address cardAddress) external view returns (uint256) { Card storage card = cards[cardAddress]; if (card.monthlySpent >= card.monthlyLimit) return 0; return card.monthlyLimit - card.monthlySpent; } } Key Features of Card Manager The Card Manager introduces several important concepts: Individual Card Limits Each card has three types of limits: Per-transaction limit: Maximum amount for a single purchase Daily limit: Maximum spending in a 24-hour period Monthly limit: Maximum spending in a 30-day period This multi-tiered approach provides granular control over spending patterns and helps prevent fraud. Automatic Limit Resets The contract automatically resets daily and monthly spending counters. The resetSpendingIfNeeded function checks if a new day or month has begun and resets the appropriate counters. This happens transparently during transaction validation. Card Lifecycle Management Cards can be issued, activated, and deactivated. A deactivated card cannot make purchases, but the card data remains on-chain for historical records. This is crucial for fraud prevention, if a card is compromised, it can be immediately deactivated without affecting other cards linked to the same account. 3. The Spending Limits Contract with Encryption Now we get to the interesting part: encrypted spending limits. This contract demonstrates how to store and validate spending rules while keeping certain parameters private using commitment schemes. // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SpendingLimits { struct EncryptedLimit { bytes32 limitCommitment; bytes32 categoryCommitment; bool isActive; uint256 validUntil; bytes32[] allowedMerchantCategories; string[] restrictedCountries; } mapping(address => mapping(uint256 => EncryptedLimit)) public cardLimits; mapping(address => uint256) public limitCount; event LimitCreated( address indexed cardAddress, uint256 limitId, bytes32 limitCommitment ); event LimitValidated(address indexed cardAddress, uint256 limitId, bool success); event LimitRevoked(address indexed cardAddress, uint256 limitId); function createEncryptedLimit( address cardAddress, bytes32 limitCommitment, bytes32 categoryCommitment, uint256 validUntil, bytes32[] memory allowedCategories, string[] memory restrictedCountries ) external returns (uint256) { uint256 limitId = limitCount[cardAddress]; cardLimits[cardAddress][limitId] = EncryptedLimit({ limitCommitment: limitCommitment, categoryCommitment: categoryCommitment, isActive: true, validUntil: validUntil, allowedMerchantCategories: allowedCategories, restrictedCountries: restrictedCountries }); limitCount[cardAddress]++; emit LimitCreated(cardAddress, limitId, limitCommitment); return limitId; } function validateTransaction( address cardAddress, uint256 limitId, uint256 amount, bytes32 merchantCategory, string memory country, bytes32 proof ) external view returns (bool) { EncryptedLimit storage limit = cardLimits[cardAddress][limitId]; require(limit.isActive, "Limit not active"); require(block.timestamp <= limit.validUntil, "Limit expired"); bytes32 computedCommitment = keccak256( abi.encodePacked(amount, merchantCategory, country, proof) ); if (computedCommitment != limit.limitCommitment) { return false; } if (!isCategoryAllowed(limit.allowedMerchantCategories, merchantCategory)) { return false; } if (isCountryRestricted(limit.restrictedCountries, country)) { return false; } return true; } function isCategoryAllowed( bytes32[] memory allowedCategories, bytes32 category ) internal pure returns (bool) { if (allowedCategories.length == 0) return true; for (uint i = 0; i < allowedCategories.length; i++) { if (allowedCategories[i] == category) return true; } return false; } function isCountryRestricted( string[] memory restrictedCountries, string memory country ) internal pure returns (bool) { for (uint i = 0; i < restrictedCountries.length; i++) { if (keccak256(bytes(restrictedCountries[i])) == keccak256(bytes(country))) { return true; } } return false; } function revokeLimit(address cardAddress, uint256 limitId) external { require(cardLimits[cardAddress][limitId].isActive, "Limit already inactive"); cardLimits[cardAddress][limitId].isActive = false; emit LimitRevoked(cardAddress, limitId); } function getLimitDetails( address cardAddress, uint256 limitId ) external view returns ( bytes32 limitCommitment, bool isActive, uint256 validUntil, bytes32[] memory allowedCategories, string[] memory restrictedCountries ) { EncryptedLimit storage limit = cardLimits[cardAddress][limitId]; return ( limit.limitCommitment, limit.isActive, limit.validUntil, limit.allowedMerchantCategories, limit.restrictedCountries ); } } How Encrypted Limits Work The encryption here uses a commitment scheme, a cryptographic technique where you commit to a value without revealing it. Creating a Commitment When you create a spending limit, instead of storing the actual amount on-chain (which would be publicly visible), you store a hash commitment: limitCommitment = keccak256(amount + merchantCategory + country + secret) The actual limit amount remains off-chain or encrypted. Only someone with the correct values can prove they're within the limit. Validating Transactions During a transaction, the card provides: The transaction amount The merchant category The country A proof (the secret used in the original commitment) The contract recomputes the commitment and checks if it matches. If it does, the transaction is within the encrypted limit. The beauty is that observers can see the transaction was validated, but they cannot see what the actual spending limit is. Merchant Categories and Geographic Restrictions Beyond amount limits, the contract supports: Merchant category codes: Restrict cards to specific types of merchants (e.g., only gas stations and groceries) Geographic restrictions: Block transactions from certain countries (useful for fraud prevention) These restrictions are stored openly because they don't reveal sensitive financial information about the cardholder. 4. The Payment Processor Contract This contract orchestrates the entire payment flow, bringing together all the previous components. // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface ICardManager { function checkAndRecordSpending(address cardAddress, uint256 amount) external returns (bool); function cards(address) external view returns ( address cardAddress, address linkedAccount, bool isActive, uint256 dailyLimit, uint256 monthlyLimit, uint256 perTransactionLimit, uint256 dailySpent, uint256 monthlySpent, uint256 lastResetDay, uint256 lastResetMonth, string memory cardholderName, bytes32 cardType ); } interface ISpendingLimits { function validateTransaction( address cardAddress, uint256 limitId, uint256 amount, bytes32 merchantCategory, string memory country, bytes32 proof ) external view returns (bool); } contract PaymentProcessor { struct Transaction { address cardAddress; address merchant; uint256 amount; bytes32 merchantCategory; string country; uint256 timestamp; TransactionStatus status; bytes32 transactionHash; } enum TransactionStatus { Pending, Approved, Declined, Settled, Refunded } ICreditFacility public creditFacility; ICardManager public cardManager; ISpendingLimits public spendingLimits; mapping(bytes32 => Transaction) public transactions; mapping(address => bytes32[]) public cardTransactions; mapping(address => bytes32[]) public merchantTransactions; uint256 public transactionCount; event TransactionInitiated( bytes32 indexed transactionHash, address indexed cardAddress, address indexed merchant, uint256 amount ); event TransactionApproved(bytes32 indexed transactionHash); event TransactionDeclined(bytes32 indexed transactionHash, string reason); event TransactionSettled(bytes32 indexed transactionHash); event TransactionRefunded(bytes32 indexed transactionHash); constructor( address _creditFacility, address _cardManager, address _spendingLimits ) { creditFacility = ICreditFacility(_creditFacility); cardManager = ICardManager(_cardManager); spendingLimits = ISpendingLimits(_spendingLimits); } function initiateTransaction( address cardAddress, address merchant, uint256 amount, bytes32 merchantCategory, string memory country, uint256 limitId, bytes32 proof ) external returns (bytes32) { bytes32 txHash = keccak256( abi.encodePacked( cardAddress, merchant, amount, merchantCategory, country, block.timestamp, transactionCount++ ) ); transactions[txHash] = Transaction({ cardAddress: cardAddress, merchant: merchant, amount: amount, merchantCategory: merchantCategory, country: country, timestamp: block.timestamp, status: TransactionStatus.Pending, transactionHash: txHash }); cardTransactions[cardAddress].push(txHash); merchantTransactions[merchant].push(txHash); emit TransactionInitiated(txHash, cardAddress, merchant, amount); bool approved = processTransaction(txHash, limitId, proof); if (approved) { emit TransactionApproved(txHash); } return txHash; } function processTransaction( bytes32 txHash, uint256 limitId, bytes32 proof ) internal returns (bool) { Transaction storage txn = transactions[txHash]; (, address linkedAccount, bool isActive, , , , , , , , , ) = cardManager.cards(txn.cardAddress); if (!isActive) { txn.status = TransactionStatus.Declined; emit TransactionDeclined(txHash, "Card not active"); return false; } bool cardLimitCheck = cardManager.checkAndRecordSpending( txn.cardAddress, txn.amount ); if (!cardLimitCheck) { txn.status = TransactionStatus.Declined; emit TransactionDeclined(txHash, "Card limit exceeded"); return false; } bool encryptedLimitCheck = spendingLimits.validateTransaction( txn.cardAddress, limitId, txn.amount, txn.merchantCategory, txn.country, proof ); if (!encryptedLimitCheck) { txn.status = TransactionStatus.Declined; emit TransactionDeclined(txHash, "Encrypted limit validation failed"); return false; } bool creditUsed = creditFacility.useCredit(linkedAccount, txn.amount); if (!creditUsed) { txn.status = TransactionStatus.Declined; emit TransactionDeclined(txHash, "Insufficient credit"); return false; } txn.status = TransactionStatus.Approved; return true; } function settleTransaction(bytes32 txHash) external { Transaction storage txn = transactions[txHash]; require(txn.status == TransactionStatus.Approved, "Transaction not approved"); txn.status = TransactionStatus.Settled; // In a real system, this would transfer funds to the merchant // payable(txn.merchant).transfer(txn.amount); emit TransactionSettled(txHash); } function refundTransaction(bytes32 txHash) external { Transaction storage txn = transactions[txHash]; require( txn.status == TransactionStatus.Settled, "Transaction not settled" ); (, address linkedAccount, , , , , , , , , , ) = cardManager.cards(txn.cardAddress); // Return credit to account // creditFacility.repayCredit{value: txn.amount}(linkedAccount, txn.amount); txn.status = TransactionStatus.Refunded; emit TransactionRefunded(txHash); } function getTransaction(bytes32 txHash) external view returns ( address cardAddress, address merchant, uint256 amount, bytes32 merchantCategory, string memory country, uint256 timestamp, TransactionStatus status ) { Transaction storage txn = transactions[txHash]; return ( txn.cardAddress, txn.merchant, txn.amount, txn.merchantCategory, txn.country, txn.timestamp, txn.status ); } function getCardTransactions(address cardAddress) external view returns (bytes32[] memory) { return cardTransactions[cardAddress]; } function getMerchantTransactions(address merchant) external view returns (bytes32[] memory) { return merchantTransactions[merchant]; } } Payment Flow Explained The Payment Processor orchestrates a complex multi-step validation process: Transaction Initiation: A transaction is created with all necessary details (card, merchant, amount, category, country) Card Validation: Check if the card is active and in good standing Card Limit Checks: Validate against daily, monthly, and per-transaction limits Encrypted Limit Validation: Verify the transaction against encrypted spending rules using the commitment proof Credit Availability: Ensure the linked account has sufficient credit Approval/Decline: If all checks pass, approve the transaction; otherwise, decline with a specific reason Settlement: After approval, the transaction is marked as settled and funds are transferred (in a production system) Refund Capability: Transactions can be refunded, returning credit to the account Transaction Status Lifecycle Transactions move through distinct states: Pending: Initial state when created Approved: All validations passed Declined: Failed one or more validations Settled: Funds transferred to merchant Refunded: Transaction reversed This status tracking provides transparency and enables proper accounting. 5. Multi-Signature Governance Contract For high-value transactions or critical system changes, we need additional oversight beyond individual card limits. // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract MultiSigGovernance { struct Proposal { uint256 proposalId; ProposalType proposalType; address targetContract; bytes callData; uint256 value; string description; uint256 createdAt; uint256 executionTime; bool executed; bool cancelled; mapping(address => bool) hasVoted; uint256 votesFor; uint256 votesAgainst; } enum ProposalType { CreditLimitIncrease, CardIssuance, SystemUpgrade, EmergencyAction, ParameterChange } address[] public governors; mapping(address => bool) public isGovernor; uint256 public quorumPercentage; uint256 public proposalCount; uint256 public votingPeriod; uint256 public timelockPeriod; mapping(uint256 => Proposal) public proposals; event ProposalCreated( uint256 indexed proposalId, ProposalType proposalType, address indexed proposer, string description ); event VoteCast( uint256 indexed proposalId, address indexed voter, bool support ); event ProposalExecuted(uint256 indexed proposalId); event ProposalCancelled(uint256 indexed proposalId); event GovernorAdded(address indexed governor); event GovernorRemoved(address indexed governor); modifier onlyGovernor() { require(isGovernor[msg.sender], "Not a governor"); _; } constructor( address[] memory _governors, uint256 _quorumPercentage, uint256 _votingPeriod, uint256 _timelockPeriod ) { require(_governors.length > 0, "Must have at least one governor"); require(_quorumPercentage > 0 && _quorumPercentage <= 100, "Invalid quorum"); for (uint i = 0; i < _governors.length; i++) { governors.push(_governors[i]); isGovernor[_governors[i]] = true; } quorumPercentage = _quorumPercentage; votingPeriod = _votingPeriod; timelockPeriod = _timelockPeriod; } function createProposal( ProposalType proposalType, address targetContract, bytes memory callData, uint256 value, string memory description ) external onlyGovernor returns (uint256) { uint256 proposalId = proposalCount++; Proposal storage proposal = proposals[proposalId]; proposal.proposalId = proposalId; proposal.proposalType = proposalType; proposal.targetContract = targetContract; proposal.callData = callData; proposal.value = value; proposal.description = description; proposal.createdAt = block.timestamp; proposal.executed = false; proposal.cancelled = false; emit ProposalCreated(proposalId, proposalType, msg.sender, description); return proposalId; } function vote(uint256 proposalId, bool support) external onlyGovernor { Proposal storage proposal = proposals[proposalId]; require(!proposal.executed, "Proposal already executed"); require(!proposal.cancelled, "Proposal cancelled"); require(!proposal.hasVoted[msg.sender], "Already voted"); require( block.timestamp <= proposal.createdAt + votingPeriod, "Voting period ended" ); proposal.hasVoted[msg.sender] = true; if (support) { proposal.votesFor++; } else { proposal.votesAgainst++; } emit VoteCast(proposalId, msg.sender, support); if (hasReachedQuorum(proposalId)) { proposal.executionTime = block.timestamp + timelockPeriod; } } function executeProposal(uint256 proposalId) external onlyGovernor { Proposal storage proposal = proposals[proposalId]; require(!proposal.executed, "Already executed"); require(!proposal.cancelled, "Proposal cancelled"); require(hasReachedQuorum(proposalId), "Quorum not reached"); require( block.timestamp >= proposal.executionTime, "Timelock not expired" ); require(proposal.executionTime > 0, "Execution time not set"); proposal.executed = true; (bool success, ) = proposal.targetContract.call{value: proposal.value}( proposal.callData ); require(success, "Execution failed"); emit ProposalExecuted(proposalId); } function cancelProposal(uint256 proposalId) external onlyGovernor { Proposal storage proposal = proposals[proposalId]; require(!proposal.executed, "Already executed"); require(!proposal.cancelled, "Already cancelled"); proposal.cancelled = true; emit ProposalCancelled(proposalId); } function hasReachedQuorum(uint256 proposalId) public view returns (bool) { Proposal storage proposal = proposals[proposalId]; uint256 totalVotes = proposal.votesFor + proposal.votesAgainst; uint256 requiredVotes = (governors.length * quorumPercentage) / 100; return totalVotes >= requiredVotes && proposal.votesFor > proposal.votesAgainst; } function addGovernor(address newGovernor) external { require(!isGovernor[newGovernor], "Already a governor"); governors.push(newGovernor); isGovernor[newGovernor] = true; emit GovernorAdded(newGovernor); } function removeGovernor(address governor) external { require(isGovernor[governor], "Not a governor"); require(governors.length > 1, "Cannot remove last governor"); isGovernor[governor] = false; for (uint i = 0; i < governors.length; i++) { if (governors[i] == governor) { governors[i] = governors[governors.length - 1]; governors.pop(); break; } } emit GovernorRemoved(governor); } function getProposalDetails(uint256 proposalId) external view returns ( ProposalType proposalType, address targetContract, string memory description, uint256 votesFor, uint256 votesAgainst, bool executed, bool cancelled ) { Proposal storage proposal = proposals[proposalId]; return ( proposal.proposalType, proposal.targetContract, proposal.description, proposal.votesFor, proposal.votesAgainst, proposal.executed, proposal.cancelled ); } function getGovernors() external view returns (address[] memory) { return governors; } } Governance Features This contract implements a sophisticated governance system with several key protections: Proposal System Any governor can create a proposal for: Increasing credit limits beyond normal thresholds Issuing special cards with elevated privileges Upgrading system contracts Emergency actions (like freezing all cards) Changing system parameters Voting Mechanism Governors vote on proposals during a voting period. The system supports: Quorum requirements: A minimum percentage of governors must participate Simple majority: More votes for than against Timelock: Even after approval, there's a delay before execution The timelock is critical, it gives governors time to react if a malicious proposal passes, potentially vetoing it before execution. Governor Management Governors can be added or removed through the governance process itself. This creates a self-governing system that can adapt over time without external intervention. Real-World Usage Example Let's walk through a complete transaction flow using all these contracts: Step 1: Setup // Deploy contracts const creditFacility = await CreditFacility.deploy(); const cardManager = await CardManager.deploy(creditFacility.address); const spendingLimits = await SpendingLimits.deploy(); const paymentProcessor = await PaymentProcessor.deploy( creditFacility.address, cardManager.address, spendingLimits.address ); // Create credit account with 3-of-5 multisig await creditFacility.createCreditAccount( accountAddress, ethers.utils.parseEther("10000"), // $10,000 credit limit [signer1, signer2, signer3, signer4, signer5], 3 // requires 3 signatures ); Step 2: Issue a Card // Issue card with spending limits await cardManager.issueCard( cardAddress, accountAddress, "John Doe", ethers.utils.parseEther("500"), // $500 daily limit ethers.utils.parseEther("5000"), // $5000 monthly limit ethers.utils.parseEther("200"), // $200 per transaction ethers.utils.formatBytes32String("STANDARD") ); Step 3: Create Encrypted Spending Limit // Create commitment for encrypted limit const secretAmount = ethers.utils.parseEther("100"); const category = ethers.utils.formatBytes32String("GROCERY"); const country = "US"; const secret = ethers.utils.randomBytes(32); const commitment = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode( ["uint256", "bytes32", "string", "bytes32"], [secretAmount, category, country, secret] ) ); // Store encrypted limit on-chain await spendingLimits.createEncryptedLimit( cardAddress, commitment, category, Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60, // valid for 1 year [ethers.utils.formatBytes32String("GROCERY")], ["RU", "KP"] // restricted countries ); Step 4: Process a Transaction // Initiate payment at grocery store const txHash = await paymentProcessor.initiateTransaction( cardAddress, merchantAddress, ethers.utils.parseEther("75"), // $75 purchase ethers.utils.formatBytes32String("GROCERY"), "US", 0, // limitId secret // proof for encrypted limit ); // Transaction automatically validated against: // 1. Card active status // 2. Daily/monthly/per-transaction limits // 3. Encrypted spending rules // 4. Available credit Step 5: Settlement // After validation, settle the transaction await paymentProcessor.settleTransaction(txHash); // Funds are now transferred to merchant // Credit account balance is updated Advanced Features and Optimizations Gas Optimization Techniques These contracts can be further optimized for production: Batch Operations: Instead of processing transactions one-by-one, implement batch processing to reduce gas costs. function batchProcessTransactions( bytes32[] memory txHashes ) external { for (uint i = 0; i < txHashes.length; i++) { settleTransaction(txHashes[i]); } } Storage Packing: Use smaller data types where possible and pack related variables. struct PackedCard { address cardAddress; // 20 bytes address linkedAccount; // 20 bytes uint128 dailyLimit; // 16 bytes uint128 monthlyLimit; // 16 bytes bool isActive; // 1 byte // Total: 73 bytes (can fit in 3 storage slots) } Event Indexing: Properly index events for efficient off-chain querying. Security Considerations Reentrancy Protection Always use the Checks-Effects-Interactions pattern: function useCredit(address account, uint256 amount) external { // Checks require(accounts[account].availableCredit >= amount); // Effects accounts[account].availableCredit -= amount; accounts[account].outstandingBalance += amount; // Interactions (external calls) emit CreditUsed(account, amount); } Access Control Implement role-based access control using OpenZeppelin: import "@openzeppelin/contracts/access/AccessControl.sol"; contract SecuredCardManager is AccessControl { bytes32 public constant ISSUER_ROLE = keccak256("ISSUER_ROLE"); bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); function issueCard(...) external onlyRole(ISSUER_ROLE) { // Card issuance logic } } Oracle Integration For real-world data (exchange rates, merchant verification), integrate Chainlink oracles: import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; contract PriceOracle { AggregatorV3Interface internal priceFeed; function getLatestPrice() public view returns (int) { (, int price, , ,) = priceFeed.latestRoundData(); return price; } } Privacy Enhancements For production systems, implement zero-knowledge proofs using libraries like ZoKrates or Circom: // Pseudocode for ZK proof circuit SpendingLimit { private input actualLimit; private input transactionAmount; public input commitment; assert(hash(actualLimit) == commitment); assert(transactionAmount <= actualLimit); } This allows truly private spending limits where even the contract cannot see the actual values. Integration with Off-Chain Systems Real-world credit card systems require integration with: Payment Networks Connect to Visa/Mastercard networks through payment gateway APIs: // Off-chain service async function processCardPayment(transaction) { // Validate with smart contract const isValid = await paymentProcessor.initiateTransaction(...); if (isValid) { // Submit to payment network await visaGateway.authorizePayment({ cardNumber: encryptedCard, amount: transaction.amount, merchant: transaction.merchant }); } } KYC/AML Compliance Implement identity verification before card issuance: async function issueCardWithKYC(user) { // Verify identity off-chain const kycResult = await kycProvider.verify(user); if (kycResult.approved) { // Issue card on-chain await cardManager.issueCard( user.cardAddress, user.accountAddress, user.name, ... ); } } Fraud Detection Use machine learning models to detect suspicious patterns: async function monitorTransactions() { const transactions = await getRecentTransactions(); for (const tx of transactions) { const riskScore = await fraudModel.analyze(tx); if (riskScore > THRESHOLD) { // Automatically freeze card await cardManager.deactivateCard(tx.cardAddress); // Alert governance await notifyGovernors(tx); } } } Deployment Strategy Testnet Deployment // deployment script async function main() { const [deployer] = await ethers.getSigners(); console.log("Deploying contracts with account:", deployer.address); // Deploy core contracts const CreditFacility = await ethers.getContractFactory("CreditFacility"); const creditFacility = await CreditFacility.deploy(); await creditFacility.deployed(); console.log("CreditFacility deployed to:", creditFacility.address); const CardManager = await ethers.getContractFactory("CardManager"); const cardManager = await CardManager.deploy(creditFacility.address); await cardManager.deployed(); console.log("CardManager deployed to:", cardManager.address); // Deploy remaining contracts... // Verify contracts on Etherscan await verify(creditFacility.address, []); await verify(cardManager.address, [creditFacility.address]); } async function verify(contractAddress, args) { await hre.run("verify:verify", { address: contractAddress, constructorArguments: args, }); } main(); Mainnet Considerations Before mainnet deployment: Complete Security Audit: Engage firms like Trail of Bits, ConsenSys Diligence, or OpenZeppelin Bug Bounty Program: Incentivize security researchers to find vulnerabilities Gradual Rollout: Start with limited users and transaction volumes Emergency Pause: Implement circuit breakers for crisis situations Upgrade Path: Use proxy patterns for upgradeable contracts Insurance: Consider DeFi insurance protocols like Nexus Mutual Future Enhancements Layer 2 Scaling Deploy on L2 solutions for lower costs: // Optimism/Arbitrum deployment const l2Provider = new ethers.providers.JsonRpcProvider(L2_RPC_URL); const l2Deployer = new ethers.Wallet(PRIVATE_KEY, l2Provider); // Same deployment script, different network Cross-Chain Interoperability Use bridges to enable cross-chain transactions: import "@chainlink/contracts/src/v0.8/interfaces/CCIPRouter.sol"; contract CrossChainPayment { function sendCrossChainPayment( uint64 destinationChain, address receiver, uint256 amount ) external { // Use Chainlink CCIP for cross-chain messaging } } Account Abstraction Integration Implement ERC-4337 for better user experience: contract CardWallet is BaseAccount { function validateUserOp( UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds ) external override returns (uint256) { // Validate card transaction as user operation } } Conclusion We've built a complete decentralized credit card system with: Multi-signature security for master accounts Flexible card management with individual spending limits Encrypted spending rules for privacy Comprehensive payment processing with full validation Governance system for high-value operations This architecture demonstrates how blockchain technology can reimagine traditional financial infrastructure. The system is transparent yet private, decentralized yet secure, and programmable in ways traditional systems cannot match. The smart contracts provided here are educational starting points. Production systems would require extensive hardening, optimization, regulatory compliance, and integration with existing financial infrastructure. Key Takeaways Separation of Concerns: Each contract handles a specific domain, making the system modular and maintainable Security Layers: Multiple validation checkpoints ensure transactions are legitimate before processing Privacy Techniques: Commitment schemes enable encrypted rules while maintaining blockchain transparency Governance: Multi-signature and voting mechanisms distribute control and prevent single points of failure Extensibility: The modular design allows adding features without rewriting core logic What's Next? In future posts, I'll explore: Advanced zero-knowledge proof implementations for complete privacy Integration with hardware wallets and biometric authentication Compliance frameworks for regulated financial products Performance optimization and Layer 2 scaling strategies Real-world case studies of blockchain payment systems Have questions or suggestions? Drop a comment below or reach out on Twitter at @ithora. If you're building something similar, I'd love to hear about your approach! Disclaimer: This code is for educational purposes only. It has not been audited and should not be used in production without comprehensive security review and testing. Financial systems involve real money and require professional security expertis --- # Building a Decentralized Credit Card System with Multi-Signature Smart Contracts URL: https://www.ivanturkovic.com/2025/12/07/building-a-decentralized-credit-card-system-with-multi-signature-smart-contracts/ Published: 2025-12-07 Topics: Startups & Business The intersection of blockchain technology and traditional financial services opens fascinating possibilities for reimagining how we handle payments and credit. In this post, I'll explore a proof-of-concept architecture for a public blockchain-based credit card system that uses multi-signature cryptography and smart contracts to manage spending limits and access controls. The Core Architecture The fundamental challenge is creating a system that maintains the security and flexibility of traditional credit cards while leveraging blockchain's transparency and programmability. Here's how we can structure this: Multi-Signature Hierarchical Key System The system relies on a hierarchical key structure with different access levels: Master Private Key: This serves as the root authority for the bank account or credit facility. Think of this as the bank's vault key—it has ultimate control over the credit line and can set global parameters. This key would be held by the issuing institution or distributed among multiple parties using threshold signatures for enhanced security. Card Private Keys: Each physical or virtual card gets its own private key. These keys are subordinate to the master key but have specific permissions and spending limits defined by smart contracts. Users interact with the payment system through these card keys, which can be revoked or modified without affecting other cards linked to the same account. Smart Contract-Enforced Spending Limits This is where blockchain technology truly shines. Rather than relying solely on centralized databases, spending limits and transaction rules are encoded into smart contracts: Smart Contract Logic: - Maximum transaction amount - Daily/monthly spending caps - Merchant category restrictions - Geographic limitations - Multi-signature requirements for large purchases The smart contract acts as an automated gatekeeper, validating every transaction against these encrypted rules before authorizing payment. Proof of Concept Implementation Public Network Considerations Building this on a public blockchain offers several advantages: Transparency: All transactions (though not necessarily personal details) can be audited publicly, reducing fraud and increasing trust. Decentralization: No single point of failure exists. The payment system continues functioning even if individual nodes go offline. Programmability: Smart contracts enable complex financial logic that adapts automatically without manual intervention. However, we need to address privacy concerns. This is where encryption becomes critical. Encrypted Spending Limits Here's the clever part: spending limits and transaction rules don't need to be publicly visible. Using techniques like: Zero-knowledge proofs: Prove a transaction is within limits without revealing the actual limit Homomorphic encryption: Perform computations on encrypted data without decrypting it Secure multi-party computation: Multiple parties can jointly compute functions while keeping inputs private The smart contract can validate transactions against encrypted rules, maintaining privacy while ensuring compliance. Access Control Flow Card Initialization: Master key creates a new card keypair and deploys a smart contract with encrypted spending parameters Transaction Request: User initiates payment using card private key Smart Contract Validation: Contract verifies signature, checks encrypted limits, validates merchant data Multi-Sig Approval (if required): For transactions exceeding certain thresholds, multiple signatures from master key holders may be required Settlement: Upon approval, funds transfer from the credit facility to the merchant Transfer Services Integration The card private key specifically interfaces with transfer services—the infrastructure that moves value between accounts. This separation of concerns means: The master key controls account-level operations (credit limits, account status) Card keys handle day-to-day transactions (purchases, transfers) Smart contracts mediate between these layers, enforcing rules automatically Security Considerations Key Management: Hardware security modules (HSMs) or secure enclaves should protect private keys. For users, integration with hardware wallets or biometric devices adds another security layer. Recovery Mechanisms: Smart contracts can include social recovery or time-locked recovery procedures if a card key is lost. Fraud Detection: While the system is decentralized, AI-powered fraud detection can still monitor transaction patterns and flag suspicious activity for additional verification. Regulatory Compliance: The system must integrate with KYC/AML procedures, potentially using privacy-preserving identity verification methods. Advantages of This Approach Programmable Money: Spending rules adapt automatically based on predefined conditions Instant Settlement: Blockchain transactions can settle much faster than traditional card networks Lower Fees: Disintermediation reduces the number of parties taking a cut Enhanced Security: Multi-signature requirements and smart contract validation add layers of protection User Control: Card holders have more transparency and control over their financial instruments Challenges to Address Scalability: Public blockchains must handle thousands of transactions per second Privacy: Balancing transparency with user privacy requires sophisticated cryptographic techniques Regulatory Uncertainty: Financial regulations vary globally and are still evolving for blockchain-based systems User Experience: The system must be as simple to use as traditional cards despite the underlying complexity Conclusion This proof-of-concept demonstrates how public blockchain networks, multi-signature cryptography, and smart contracts can work together to create a more secure, transparent, and flexible credit card system. By encrypting spending limits and using hierarchical key structures, we can maintain privacy while leveraging blockchain's strengths. The future of payments likely involves hybrid approaches—combining the best aspects of traditional financial infrastructure with blockchain innovation. As the technology matures and regulations clarify, we'll see more sophisticated implementations of these concepts moving from proof-of-concept to production systems. What aspects of blockchain-based payment systems interest you most? Are there specific technical challenges you'd like me to explore in future posts? This post explores theoretical architecture for educational purposes. Actual implementation would require extensive security audits, regulatory approval, and collaboration with financial institutions. --- # Ruby 5.0: What If Ruby Had First-Class Types? URL: https://www.ivanturkovic.com/2025/12/05/typed-ruby-what-if-ruby-had-first-class-types/ Published: 2025-12-05 Topics: Career & Leadership, Ruby on Rails, Software Engineering After imagining a typed CoffeeScript, I realized we need to go deeper. CoffeeScript was inspired by Ruby, but what about Ruby itself? Ruby has always been beautifully expressive, but it's also been dynamically typed from day one. And while Sorbet and RBS have tried to add types, they feel bolted on. Awkward. Not quite Ruby. What if Ruby had been designed with types from the beginning? Not as an afterthought, not as a separate file you maintain, but as a natural, optional part of the language itself? Let's explore what that could look like. The Problem with Sorbet and RBS Before we reimagine Ruby with types, let's acknowledge why the current solutions haven't caught on widely. Sorbet requires you to add # typed: true comments and use a separate type checker. Types look like this: # typed: true extend T::Sig sig { params(name: String, age: Integer).returns(String) } def greet(name, age) "Hello #{name}, you are #{age}" end RBS requires separate .rbs files with type signatures: # user.rbs class User attr_reader name: String attr_reader age: Integer def initialize: (name: String, age: Integer) -> void def greet: () -> String end Both solutions have the same fundamental problem: they don't feel like Ruby. Sorbet's sig blocks are verbose and repetitive. RBS splits your code across multiple files, breaking the single-file mental model that makes Ruby so pleasant. What we need is something that feels native. Something Matz might have designed if static typing had been a priority in 1995. Core Design Principles Let's establish what TypedRuby should be: Types are optional everywhere. You can gradually type your codebase. Types are inline. No separate files, no sig blocks. Types feel like Ruby. Natural syntax that matches Ruby's philosophy. Duck typing coexists with static typing. You choose when to be strict. Generic types are first-class. Collections, custom classes, everything. The syntax is minimal. Ruby is beautiful; types shouldn't ruin that. Basic Type Annotations In TypeScript, you use colons. In Sorbet, you use sig blocks. TypedRuby could use a more natural Ruby approach with the :: operator we already know: # Current Ruby name = "Ivan" age = 30 # TypedRuby with inline types name :: String = "Ivan" age :: Integer = 30 # Or with type inference name = "Ivan" # inferred as String age = 30 # inferred as Integer The :: operator already means "scope resolution" in Ruby, but in this context (before assignment), it means "has type". It's familiar to Ruby developers and reads naturally. Method Signatures Current Sorbet approach: extend T::Sig sig { params(name: String, age: T.nilable(Integer)).returns(String) } def greet(name, age = nil) age ? "Hello #{name}, #{age}" : "Hello #{name}" end TypedRuby approach: def greet(name :: String, age :: Integer? = nil) :: String age ? "Hello #{name}, #{age}" : "Hello #{name}" end Or with Ruby 3's endless method syntax: def greet(name :: String, age :: Integer? = nil) :: String = age ? "Hello #{name}, #{age}" : "Hello #{name}" Much cleaner. The types are right there with the parameters, and the return type is at the end where it reads naturally: "define greet with these parameters, returning a String." Classes and Attributes Current approach with Sorbet: class User extend T::Sig sig { returns(String) } attr_reader :name sig { returns(Integer) } attr_reader :age sig { params(name: String, age: Integer).void } def initialize(name, age) @name = name @age = age end end TypedRuby approach: class User attr_reader of String, :name attr_reader of Integer, :age def initialize(@name :: String, @age :: Integer) end def birthday :: void @age += 1 end def greet :: String "I'm #{@name}, #{@age} years old" end end Even better, we could introduce parameter properties like TypeScript: class User def initialize(@name :: String, @age :: Integer, @email :: String) # @name, @age, and @email are automatically instance variables end end Generics: The Ruby Way This is where it gets interesting. Ruby already has a beautiful way of working with collections. TypedRuby needs to extend that naturally. TypeScript uses angle brackets: class Container { private value: T; constructor(value: T) { this.value = value; } } Sorbet uses square brackets: class Container extend T::Generic T = type_member sig { params(value: T).void } def initialize(value) @value = value end end TypedRuby could use a more natural syntax with of: class Container of T def initialize(@value :: T) end def get :: T @value end def map of U, &block :: (T) -> U :: Container of U Container.new(yield @value) end end # Usage container = Container of String with.new("hello") lengths = container.map { |s| s.length } # Container of Integer For multiple type parameters: class Pair of K, V def initialize(@key :: K, @value :: V) end def map_value of U, &block :: (V) -> U :: Pair of K, U Pair.new(@key, yield @value) end end Generic Methods Methods can be generic too: def identity of T, value :: T :: T value end def find_first of T, items :: Array of T, &predicate :: (T) -> Boolean :: T? items.find(&predicate) end # Usage result = find_first([1, 2, 3, 4]) { |n| n > 2 } # Integer? Array and Hash Types Ruby's arrays and hashes need type support: # Arrays numbers :: Array of Integer = [1, 2, 3, 4, 5] names :: Array of String = ["Alice", "Bob", "Charlie"] # Or using shorthand numbers :: [Integer] = [1, 2, 3, 4, 5] names :: [String] = ["Alice", "Bob", "Charlie"] # Hashes user_ages :: Hash of String, Integer = { "Alice" => 30, "Bob" => 25 } # Or using shorthand user_ages :: {String => Integer} = { "Alice" => 30, "Bob" => 25 } # Symbol keys (very common in Ruby) config :: {Symbol => String} = { host: "localhost", port: "3000" } Union Types Ruby's dynamic nature often uses union types implicitly. Let's make it explicit: # TypeScript: string | number value :: String | Integer = "hello" value = 42 # OK # Method with union return type def find_user(id :: Integer) :: User | nil User.find_by(id: id) end # Multiple unions status :: "pending" | "active" | "completed" = "pending" Nullable Types Ruby uses nil everywhere. TypedRuby needs to handle this elegantly: # The ? suffix means "or nil" name :: String? = nil name = "Ivan" # OK # Methods that might return nil def find_user(id :: Integer) :: User? User.find_by(id: id) end # Safe navigation works with types user :: User? = find_user(123) email = user&.email # String? inferred Interfaces and Modules Ruby uses modules for interfaces. TypedRuby could extend this: interface Comparable of T def <=>(other :: T) :: Integer end interface Enumerable of T def each(&block :: (T) -> void) :: void end # Implementation class User include Comparable of User attr_reader :name :: String def initialize(@name :: String) end def <=>(other :: User) :: Integer name <=> other.name end end Type Aliases Creating reusable type definitions: type UserId = Integer type Email = String type UserStatus = "active" | "inactive" | "banned" type Result of T = { success: true, value: T } | { success: false, error: String } def create_user(name :: String) :: Result of User user = User.create(name: name) if user.persisted? { success: true, value: user } else { success: false, error: user.errors.full_messages.join(", ") } end end Practical Example: A Repository Pattern Let's build something real. Here's a generic repository in TypedRuby: interface Repository of T def find(id :: Integer) :: T? def all :: [T] def create(attributes :: Hash) :: T def update(id :: Integer, attributes :: Hash) :: T? def delete(id :: Integer) :: Boolean end class ActiveRecordRepository of T implements Repository of T def initialize(@model_class :: Class) end def find(id :: Integer) :: T? @model_class.find_by(id: id) end def all :: [T] @model_class.all.to_a end def create(attributes :: Hash) :: T @model_class.create!(attributes) end def update(id :: Integer, attributes :: Hash) :: T? record = find(id) return nil unless record record.update!(attributes) record end def delete(id :: Integer) :: Boolean record = find(id) return false unless record record.destroy! true end end # Usage user_repo = ActiveRecordRepository of User .new(User) users :: [User] = user_repo.all user :: User? = user_repo.find(123) Blocks and Procs with Types Blocks are fundamental to Ruby. They need proper type support: # Block parameter types def map of T, U, items :: [T], &block :: (T) -> U :: [U] items.map(&block) end # Proc types callback :: Proc of (String) -> void = ->(msg) { puts msg } transformer :: Proc of (Integer) -> String = ->(n) { n.to_s } # Lambda types double :: Lambda of (Integer) -> Integer = ->(x) { x * 2 } # Method that accepts a block with types def with_timing of T, &block :: () -> T :: T start_time = Time.now result = yield duration = Time.now - start_time puts "Took #{duration} seconds" result end # Usage result :: String = with_timing { expensive_operation() } Rails Integration Ruby is often Rails. TypedRuby needs to work beautifully with Rails. Here's where we need to think carefully about syntax. For method calls that take parameters, we can use a generic-style syntax that feels natural. Generic-style method calls for associations: class User < ApplicationRecord # Using 'of' with method calls (like generic instantiation) has_many of Post, :posts belongs_to of Company, :company has_one of Profile?, :profile # Or postfix style (reads more naturally) has_many :posts of Post belongs_to :company of Company has_one :profile of Profile? # For validations, types on the attribute names validates :email of String, presence: true, uniqueness: true validates :age of Integer, numericality: { greater_than: 0 } # Scopes with return types scope :active of Relation[User], -> { where(status: "active") } scope :by_name of Relation[User], ->(name :: String) { where("name LIKE ?", "%#{name}%") } # Typed callbacks still use :: for return types before_save :normalize_email def normalize_email :: void self.email = email.downcase.strip end # Typed instance methods def full_name :: String "#{first_name} #{last_name}" end def posts_count :: Integer posts.count end end Alternative: Square bracket syntax (like actual generics): class User < ApplicationRecord # Using square brackets like generic type parameters has_many[Post] :posts belongs_to[Company] :company has_one[Profile?] :profile # With additional options has_many[Post] :posts, dependent: :destroy has_many[Comment] :comments, through: :posts # Validations validates[String] :email, presence: true, uniqueness: true validates[Integer] :age, numericality: { greater_than: 0 } # Scopes scope[Relation[User]] :active, -> { where(status: "active") } scope[Relation[User]] :by_name, ->(name :: String) { where("name LIKE ?", "%#{name}%") } end Comparison of syntaxes: # Option 1: Postfix 'of' (most Ruby-like) has_many :posts of Post validates :email of String, presence: true # Option 2: Prefix 'of' (generic-like) has_many of Post, :posts validates of String, :email, presence: true # Option 3: Square brackets (actual generics) has_many[Post] :posts validates[String] :email, presence: true # Option 4: 'as:' keyword (traditional keyword argument) has_many :posts, as: [Post] validates :email, as: String, presence: true # Option 5: '<>' Angled brackets (traditional keyword argument) has_many<[Post]> :posts validates :email, presence: true I personally prefer Option 2 (prefix 'of') because: It reads naturally in English: "has many of Post type" The symbol comes first (Ruby convention) It's unambiguous and parser-friendly It feels like a natural Ruby extension Full Rails example with postfix 'of': class User < ApplicationRecord has_many :posts of Post, dependent: :destroy has_many :comments of Comment, through: :posts belongs_to :company of Company has_one :profile of Profile? validates :email of String, presence: true, uniqueness: true validates :age of Integer, numericality: { greater_than: 0 } validates :status of "active" | "inactive" | "banned", inclusion: { in: %w[active inactive banned] } scope :active of Relation[User], -> { where(status: "active") } scope :by_company of Relation[User], ->(company_id :: Integer) { where(company_id: company_id) } before_save :normalize_email after_create :send_welcome_email def normalize_email :: void self.email = email.downcase.strip end def full_name :: String "#{first_name} #{last_name}" end def recent_posts(limit :: Integer = 10) :: [Post] posts.order(created_at: :desc).limit(limit).to_a end end class PostsController < ApplicationController def index :: void @posts :: [Post] = Post.includes(:user).order(created_at: :desc) end def show :: void @post :: Post = Post.find(params[:id]) end def create :: void @post :: Post = Post.new(post_params) if @post.save redirect_to @post, notice: "Post created" else render :new, status: :unprocessable_entity end end private def post_params :: Hash params.require(:post).permit(:title, :body, :user_id) end end How it works under the hood: The of keyword in method calls would be syntactic sugar that the parser recognizes: # What you write: has_many :posts of Post # What the parser sees: has_many(:posts, __type__: Post) # Rails can then use this: def has_many(name, **options) type = options.delete(:__type__) # Define the association define_method(name) do # ... normal association logic end # Store type information for runtime validation/documentation if type association_types[name] = type # Optional runtime validation in development if Rails.env.development? define_method(name) do result = super() validate_type!(result, type) result end end end end This approach: Keeps the symbol first (Ruby convention) Uses familiar of keyword (like we use for generics) Works with all existing parameters Is parser-friendly and unambiguous Reads naturally in English Complex Example: A Service Object Let's build a realistic service object with full type safety: type TransferResult = { success: true, transaction: Transaction } | { success: false, error: String } class MoneyTransferService def initialize( @from_account :: Account, @to_account :: Account, @amount :: BigDecimal ) end def call :: TransferResult return error("Amount must be positive") if @amount <= 0 return error("Insufficient funds") if @from_account.balance < @amount return error("Accounts must be different") if @from_account == @to_account transaction :: Transaction? = nil Account.transaction do @from_account.withdraw(@amount) @to_account.deposit(@amount) transaction = Transaction.create!( from_account: @from_account, to_account: @to_account, amount: @amount, status: "completed" ) end { success: true, transaction: transaction } rescue ActiveRecord::RecordInvalid => e error(e.message) end private def error(message :: String) :: TransferResult { success: false, error: message } end end # Usage service = MoneyTransferService.new(from_account, to_account, 100.50) result :: TransferResult = service.call case result in { success: true, transaction: tx } puts "Transfer successful: #{tx.id}" in { success: false, error: err } puts "Transfer failed: #{err}" end Pattern Matching with Types Ruby 3 introduced pattern matching. TypedRuby makes it type-safe: type Response of T = { status: "ok", data: T } | { status: "error", message: String } | { status: "loading" } def handle_response of T, response :: Response of T :: String case response in { status: "ok", data: data :: T } "Success: #{data}" in { status: "error", message: msg :: String } "Error: #{msg}" in { status: "loading" } "Loading..." end end # Usage user_response :: Response of User = fetch_user(123) message = handle_response(user_response) Metaprogramming with Types Ruby's metaprogramming is powerful but dangerous. TypedRuby could make it safer: class Model def self.has_typed_attribute of T, name :: Symbol, type :: Class define_method(name) :: T do instance_variable_get("@#{name}") end define_method("#{name}=") :: void do |value :: T| instance_variable_set("@#{name}", value) end end end class User < Model has_typed_attribute of String, :name, String has_typed_attribute of Integer, :age, Integer end user = User.new user.name = "Ivan" # OK user.age = 30 # OK user.name = 123 # Type error! Gradual Typing The beauty of TypedRuby is that it's optional. You can mix typed and untyped code: # Completely untyped (classic Ruby) def process(data) data.map { |x| x * 2 } end # Partially typed def process(data :: Array) data.map { |x| x * 2 } end # Fully typed def process of T, data :: [T], &block :: (T) -> T :: [T] data.map(&block) end # The three can coexist in the same codebase Type System and Object Hierarchy Here's a crucial question: how do types relate to Ruby's object system? In Ruby, everything is an object, and every class inherits from Object (or BasicObject). TypedRuby's type system needs to respect this. Types ARE classes (mostly) In TypedRuby, most types would literally be the classes themselves: # String is both a class and a type name :: String = "Ivan" puts String.class # => Class puts String.ancestors # => [String, Comparable, Object, Kernel, BasicObject] # User is both a class and a type user :: User = User.new puts User.class # => Class puts User.ancestors # => [User, ApplicationRecord, ActiveRecord::Base, Object, ...] This is fundamentally different from TypeScript, where types exist only at compile time. In TypedRuby, types are runtime objects too. Special type constructors Some type syntax creates type objects at runtime: # Array type constructor posts :: [Post] = [] # This is roughly equivalent to: posts :: Array[Post] = [] # Which could be implemented as: class Array def self.[](element_type) TypedArray.new(element_type) end end # Hash type constructor ages :: {String => Integer} = {} # Roughly: ages :: Hash[String, Integer] = {} The Type class hierarchy TypedRuby would introduce a parallel type hierarchy: # New base classes for type system class Type # Base class for all types end class GenericType < Type # For parameterized types like Array[T], Hash[K,V] attr_reader :type_params def initialize(*type_params) @type_params = type_params end end class UnionType < Type # For union types like String | Integer attr_reader :types def initialize(*types) @types = types end end class NullableType < Type # For nullable types like String? attr_reader :inner_type def initialize(inner_type) @inner_type = inner_type end end # These would be used like: array_of_posts = GenericType.new(Array, Post) # [Post] string_or_int = UnionType.new(String, Integer) # String | Integer nullable_user = NullableType.new(User) # User? Runtime type checking Because types are objects, you could check them at runtime: def process(value :: String | Integer) case value when String value.upcase when Integer value * 2 end end # The type annotation creates a runtime check: def process(value) # Compiler inserts: unless value.is_a?(String) || value.is_a?(Integer) raise TypeError, "Expected String | Integer, got #{value.class}" end case value when String value.upcase when Integer value * 2 end end Type as values (reflection) Types being objects means you can work with them: def type_info of T, value :: T :: Hash { value: value, type: T, class: value.class, ancestors: T.ancestors } end result = type_info("hello") puts result[:type] # => String puts result[:class] # => String puts result[:ancestors] # => [String, Comparable, Object, ...] # Generic types are objects too: array_type = Array of String puts array_type.class # => GenericType puts array_type.type_params # => [String] Method objects with type information Ruby's Method objects could expose type information: class User def greet(name :: String) :: String "Hello, #{name}" end end method = User.instance_method(:greet) puts method.parameter_types # => [String] puts method.return_type # => String # This enables runtime validation: def call_safely(obj, method_name, *args) method = obj.method(method_name) # Check argument types method.parameter_types.each_with_index do |type, i| unless args[i].is_a?(type) raise TypeError, "Argument #{i} must be #{type}" end end obj.send(method_name, *args) end Duck typing still works Even with types, Ruby's duck typing philosophy is preserved: # You can still use duck typing without types def quack(duck) duck.quack end # Or enforce types when you want safety def quack(duck :: Duck) :: String duck.quack end # Or use interfaces for structural typing interface Quackable def quack :: String end def quack(duck :: Quackable) :: String duck.quack # Works with any object that implements quack end Type compatibility and inheritance Types follow Ruby's inheritance rules: class Animal def speak :: String "Some sound" end end class Dog < Animal def speak :: String "Woof" end end # Dog is a subtype of Animal def make_speak(animal :: Animal) :: String animal.speak end dog = Dog.new make_speak(dog) # OK, Dog < Animal # Liskov Substitution Principle applies animals :: [Animal] = [Dog.new, Cat.new, Bird.new] The as: keyword and runtime behavior When you write: has_many :posts, as: [Post] This could be expanded by the Rails framework to: has_many :posts, type_checker: -> (value) { value.is_a?(Array) && value.all? { |item| item.is_a?(Post) } } Rails could use this for runtime validation in development mode, giving you immediate feedback if you accidentally assign the wrong type. Performance considerations Runtime type checking has overhead. TypedRuby could handle this smartly: # In development/test: full runtime checking ENV['RUBY_TYPE_CHECKING'] = 'strict' # In production: types checked only at compile time ENV['RUBY_TYPE_CHECKING'] = 'none' # Or selective checking for critical paths ENV['RUBY_TYPE_CHECKING'] = 'public_apis' Integration with existing Ruby Since types are objects, they integrate seamlessly: # Works with reflection User.instance_methods.each do |method| m = User.instance_method(method) if m.respond_to?(:return_type) puts "#{method} returns #{m.return_type}" end end # Works with metaprogramming class User [:name, :email, :age].each do |attr| define_method(attr) :: String do instance_variable_get("@#{attr}") end end end # Works with monkey patching (for better or worse) class String def original_upcase :: String # Type information is preserved end end This approach makes TypedRuby feel like a natural evolution of Ruby rather than a foreign type system bolted on. Types are just objects, following Ruby's "everything is an object" philosophy. TypedRuby should infer types aggressively: # Inferred from literal name = "Ivan" # String inferred # Inferred from method return def get_age 30 end age = get_age # Integer inferred # Inferred from array contents numbers = [1, 2, 3, 4] # [Integer] inferred # Inferred from hash user = { name: "Ivan", age: 30, active: true } # {Symbol => String | Integer | Boolean} inferred # Explicit typing when inference isn't enough mixed :: [Integer | String] = [1, "two", 3] Why This Could Work Unlike Sorbet and RBS, TypedRuby would be: Native: Types are part of the language syntax, not bolted on Optional: You choose where to add types Gradual: Mix typed and untyped code freely Readable: Syntax feels like Ruby, not like Java Powerful: Full generics, unions, intersections, pattern matching Practical: Works with Rails, metaprogramming, blocks, procs The syntax respects Ruby's philosophy. It's minimal, expressive, and doesn't get in your way. When you want types, they're there. When you don't, they're not. The Implementation Challenge Could this be built? Technically, yes. You'd need to: Extend the Ruby parser to recognize type annotations Build a type checker that understands Ruby's semantics Make it work with Ruby's dynamic features Integrate with existing tools (RuboCop, RubyMine, VS Code) Handle the massive existing Ruby ecosystem The hard part isn't the syntax. It's making the type checker smart enough to handle Ruby's dynamism while still being useful. Ruby's metaprogramming, method_missing, dynamic dispatch, these all make static typing hard. But not impossible. Crystal proved you can have Ruby-like syntax with static types. Sorbet proved you can add types to Ruby code. TypedRuby would combine the best of both: native syntax with gradual typing. The Dream Imagine opening a Rails codebase and seeing: class User < ApplicationRecord has_many :posts :: [Post] def full_name :: String "#{first_name} #{last_name}" end end class PostsController < ApplicationController def create :: void @post :: Post = Post.new(post_params) @post.save! redirect_to @post end end The types are there when you need them, documenting the code and catching bugs. But they don't dominate. The code still looks like Ruby. It still feels like Ruby. That's what TypedRuby could be. Not a separate type system bolted onto Ruby. Not a different language inspired by Ruby. But Ruby itself, evolved to support the type safety modern developers expect. Would It Succeed? Honestly? Probably not. Ruby's community values dynamism and flexibility. Matz has explicitly said he doesn't want mandatory typing. The ecosystem is built on duck typing and metaprogramming. But that doesn't mean it wouldn't be useful. A significant portion of Ruby developers would adopt optional typing if it felt natural. Rails applications would benefit from type safety in controllers, models, and services. API clients would be more reliable. Refactoring would be safer. The key is making it optional and making it Ruby. Not Sorbet's verbose sig blocks. Not RBS's separate files. Just Ruby, with types when you want them. Conclusion TypedRuby is a thought experiment, but it's a valuable one. It shows what's possible when you design types into a language from the start, rather than bolting them on later. Ruby is beautiful. Types don't have to ruin that beauty. With the right syntax, the right philosophy, and the right implementation, they could enhance it. Maybe someday we'll see Ruby 4.0 with native, optional type annotations. Maybe we won't. But it's fun to imagine a world where Ruby has the expressiveness we love and the type safety we need. Until then, we have Sorbet and RBS. They're not perfect, but they're what we've got. And who knows? Maybe they'll evolve. Maybe the syntax will improve. Maybe they'll feel more Ruby-like over time. Or maybe someone will read this and decide to build TypedRuby for real. A developer can dream. --- # TypedScript: Imagining CoffeeScript with Types URL: https://www.ivanturkovic.com/2025/12/03/typedscript-imagining-coffeescript-with-types/ Published: 2025-12-03 Topics: Career & Leadership, Ruby on Rails After writing my love letter to CoffeeScript, I couldn't stop thinking: what if CoffeeScript had embraced types instead of fading away? What if someone had built a typed version that kept all the syntactic elegance while adding the type safety that makes TypeScript so powerful? Let's imagine that world. Let's design what I'll call "TypedScript" (or maybe CoffeeType? TypedCoffee? We'll workshop the name). The goal: keep everything that made CoffeeScript beautiful while adding first-class support for types and generics. The Core Principles Before we dive into syntax, let's establish what we're trying to achieve: Types should be optional but encouraged. You can write untyped code and gradually add types. Syntax should stay clean. No angle brackets everywhere, no visual noise. Type inference should be aggressive. The compiler should figure out as much as possible. Generics should be elegant. No mess. The Ruby/Python aesthetic must be preserved. Significant whitespace, minimal punctuation, readable code. Basic Type Annotations Let's start simple. In TypeScript, you write: const name: string = "Ivan"; const age: number = 30; const isActive: boolean = true; In TypedScript, I'd imagine: name: String = "Ivan" age: Number = 30 isActive: Boolean = true Or with type inference (which should work most of the time): name = "Ivan" # inferred as String age = 30 # inferred as Number isActive = true # inferred as Boolean The colon for type annotations feels natural. It's what TypeScript uses, and it doesn't clash with CoffeeScript's existing syntax. Function Signatures TypeScript function types can get verbose: function greet(name: string, age?: number): string { return age ? `Hello ${name}, you are ${age}` : `Hello ${name}`; } const add = (a: number, b: number): number => a + b; TypedScript could look like this: greet = (name: String, age?: Number) -> String if age? "Hello #{name}, you are #{age}" else "Hello #{name}" add = (a: Number, b: Number) -> Number a + b Even cleaner with inference: greet = (name: String, age?: Number) -> if age? "Hello #{name}, you are #{age}" else "Hello #{name}" add = (a: Number, b: Number) -> a + b The return type is inferred from the actual return value. This is already how CoffeeScript works (implicit returns), so we just layer types on top. Interfaces and Type Definitions TypeScript interfaces are pretty clean, but they still require curly braces: interface User { id: string; name: string; email: string; age?: number; roles: string[]; } In TypedScript, we could use indentation: type User id: String name: String email: String age?: Number roles: [String] Or for inline types: user: {id: String, name: String, email: String} Arrays could use the Ruby-inspired [Type] syntax. Tuples could be [String, Number]. Maps could be {String: User}. Classes with Types TypeScript classes are already pretty good, but they're still verbose: class UserService { private users: User[] = []; constructor(private apiClient: ApiClient) {} async getUser(id: string): Promise { const response = await this.apiClient.get(`/users/${id}`); return response.data; } addUser(user: User): void { this.users.push(user); } } TypedScript version: class UserService users: [User] = [] constructor: (@apiClient: ApiClient) -> getUser: (id: String) -> Promise response = await @apiClient.get "/users/#{id}" response.data addUser: (user: User) -> Void @users.push user The @ syntax for instance variables is preserved, and we just add type annotations where needed. Constructor parameter properties (@apiClient: ApiClient) combine declaration and assignment in one elegant line. Generics: The Tricky Part This is where TypeScript gets ugly. Generics in TypeScript look like this: class Container { private value: T; constructor(value: T) { this.value = value; } map(fn: (value: T) => U): Container { return new Container(fn(this.value)); } } function identity(value: T): T { return value; } const result = identity("hello"); The angle brackets are noisy, and they clash with comparison operators. TypedScript needs a different approach. What if we used a more natural syntax inspired by mathematical notation? class Container of T value: T constructor: (@value: T) -> map: (fn: (T) -> U) -> Container of U for any U new Container fn(@value) identity = (value: T) -> T for any T value result = identity "hello" # type inferred The of keyword introduces type parameters for classes. The for any T suffix introduces type parameters for functions. When calling generic functions, types are inferred automatically in most cases. For multiple type parameters: class Pair of K, V constructor: (@key: K, @value: V) -> map: (fn: (V) -> U) -> Pair of K, U for any U new Pair @key, fn(@value) Union Types and Intersections TypeScript uses | for unions and & for intersections: type Result = Success | Error; type Employee = Person & Worker; TypedScript could keep this, but make it more readable: type Result = Success | Error type Employee = Person & Worker # Or with more complex types type Response = | {status: "success", data: User} | {status: "error", message: String} Advanced Generic Constraints TypeScript has complex generic constraints: function findMax(items: T[]): T { return items.reduce((max, item) => item.compareTo(max) > 0 ? item : max ); } In TypedScript: findMax = (items: [T]) -> T for any T extends Comparable items.reduce (max, item) -> if item.compareTo(max) > 0 then item else max Practical Example: Building a Generic Repository Let's build something real. Here's a TypeScript generic repository: interface Repository { findById(id: string): Promise; findAll(): Promise; save(entity: T): Promise; delete(id: string): Promise; } class ApiRepository implements Repository { constructor( private endpoint: string, private client: HttpClient ) {} async findById(id: string): Promise { try { const response = await this.client.get(`${this.endpoint}/${id}`); return response.data; } catch (error) { return null; } } async findAll(): Promise { const response = await this.client.get(this.endpoint); return response.data; } async save(entity: T): Promise { const response = await this.client.post(this.endpoint, entity); return response.data; } async delete(id: string): Promise { await this.client.delete(`${this.endpoint}/${id}`); } } The TypedScript version: interface Repository of T findById: (id: String) -> Promise findAll: () -> Promise<[T]> save: (entity: T) -> Promise delete: (id: String) -> Promise class ApiRepository of T implements Repository of T constructor: (@endpoint: String, @client: HttpClient) -> findById: (id: String) -> Promise try response = await @client.get "#{@endpoint}/#{id}" response.data catch error null findAll: () -> Promise<[T]> response = await @client.get @endpoint response.data save: (entity: T) -> Promise response = await @client.post @endpoint, entity response.data delete: (id: String) -> Promise await @client.delete "#{@endpoint}/#{id}" # Usage userRepo = new ApiRepository of User "users", httpClient users = await userRepo.findAll() Look at how clean that is. No angle brackets, no semicolons, no excessive braces. The type information is there, but it doesn't dominate the code. Type Guards and Narrowing TypeScript's type guards work well: function isString(value: unknown): value is string { return typeof value === "string"; } if (isString(data)) { console.log(data.toUpperCase()); } TypedScript could use a similar pattern: isString = (value: Unknown) -> value is String typeof value == "string" if isString data console.log data.toUpperCase() Utility Types TypeScript has utility types like Partial, Pick, Omit. These could work in TypedScript with a more natural syntax: # TypeScript type PartialUser = Partial; type UserPreview = Pick; type UserWithoutEmail = Omit; # TypedScript type PartialUser = Partial of User type UserPreview = Pick of User, "id" | "name" type UserWithoutEmail = Omit of User, "email" The Existential Operator with Types Remember CoffeeScript's beloved ? operator? It would work beautifully with nullable types: user: User? = await findUser id # User | null name = user?.name ? "Guest" user?.profile?.update() callback?() The ? in User? means nullable, just like TypeScript's User | null or User | undefined. Real-World Example: A Todo App Let's put it all together with a realistic example: type Todo id: String title: String completed: Boolean createdAt: Date type TodoFilter = "all" | "active" | "completed" class TodoStore todos: [Todo] = [] filter: TodoFilter = "all" constructor: (@storage: Storage) -> @loadTodos() loadTodos: () -> Void data = @storage.get "todos" @todos = if data? then JSON.parse data else [] saveTodos: () -> Void @storage.set "todos", JSON.stringify @todos addTodo: (title: String) -> Todo todo: Todo = id: generateId() title: title completed: false createdAt: new Date() @todos.push todo @saveTodos() todo toggleTodo: (id: String) -> Boolean todo = @todos.find (t) -> t.id == id return false unless todo? todo.completed = !todo.completed @saveTodos() true deleteTodo: (id: String) -> Boolean index = @todos.findIndex (t) -> t.id == id return false if index == -1 @todos.splice index, 1 @saveTodos() true getFilteredTodos: () -> [Todo] switch @filter when "active" then @todos.filter (t) -> !t.completed when "completed" then @todos.filter (t) -> t.completed else @todos generateId = () -> String Math.random().toString(36).substr 2, 9 Compare that to the TypeScript equivalent and tell me it isn't more elegant. The types are there, providing safety and documentation, but they don't overwhelm the code. You can still read it naturally. Why This Matters TypeScript won because it added types to JavaScript without fundamentally changing the language. That was smart from an adoption standpoint. But it meant keeping JavaScript's verbose syntax. If TypedScript had existed, we could have had both: the elegance of CoffeeScript and the safety of TypeScript. We could write code that's both beautiful and robust. The tragedy is that this never happened. CoffeeScript's creator, Jeremy Ashkenas, explicitly rejected adding types. He felt they went against CoffeeScript's philosophy of simplicity. Meanwhile, TypeScript embraced JavaScript's syntax for compatibility. Could This Still Happen? Technically, someone could build this. The CoffeeScript compiler is open source. TypeScript's type system is well-documented. A sufficiently motivated team could fork CoffeeScript and add a type system. But would anyone use it? Probably not. The JavaScript ecosystem has moved on. TypeScript has won. The tooling, the community, the momentum are all there. Starting a new compile-to-JavaScript language in 2025 would be fighting an uphill battle. Still, it's fun to imagine. And who knows? Maybe in some parallel universe, TypedScript is the dominant language for web development, and developers there are writing beautiful, type-safe code that makes our TypeScript look verbose and clunky. A developer can dream. The Syntax Reference For anyone curious, here's a quick reference of what TypedScript syntax could look like: # Basic types name: String = "Ivan" age: Number = 30 active: Boolean = true data: Any = anything() nothing: Void = undefined # Arrays and tuples numbers: [Number] = [1, 2, 3] tuple: [String, Number] = ["Ivan", 30] # Objects user: {name: String, age: Number} = {name: "Ivan", age: 30} # Nullable types optional: String? = null # Union types status: "pending" | "active" | "complete" = "pending" value: String | Number = 42 # Functions greet: (name: String) -> String = (name) -> "Hello #{name}" # Generic functions identity = (value: T) -> T for any T value # Generic classes class Container of T value: T constructor: (@value: T) -> # Interfaces interface Comparable of T compareTo: (other: T) -> Number # Type aliases type UserId = String type Result of T = {ok: true, value: T} | {ok: false, error: String} # Constraints sorted = (items: [T]) -> [T] for any T extends Comparable of T items.sort (a, b) -> a.compareTo b Closing Thoughts Would TypedScript be better than TypeScript? For me, yes. The cleaner syntax, the Ruby-inspired aesthetics, the focus on readability, all while keeping the benefits of static typing. It would be the best of both worlds. But "better" is subjective. TypeScript's compatibility with JavaScript is a huge advantage. Its massive ecosystem is irreplaceable. Its tooling is mature and battle-tested. TypedScript would be a beautiful language that few people use. And maybe that's okay. Not every good idea wins. Sometimes the practical choice beats the elegant one. But I still wish I could write my production code in TypedScript. I think it would be a joy. What do you think? Would you use TypedScript if it existed? What syntax choices would you make differently? Let me know in the comments. --- # A Love Letter to CoffeeScript and HAML: When Rails Frontend Development Was Pure Joy URL: https://www.ivanturkovic.com/2025/12/01/a-love-letter-to-coffeescript-and-haml-when-rails-frontend-development-was-pure-joy/ Published: 2025-12-01 Topics: Ruby on Rails There's something bittersweet about looking back at old codebases. Recently, I found myself diving into a Ruby on Rails project from 2012, and I was immediately transported back to an era when frontend development felt different. Better, even. The stack was CoffeeScript, HAML, and Rails' asset pipeline, and you know what? It was glorious. I know what you're thinking. "CoffeeScript? That thing died years ago. TypeScript won. Get over it." And you're right. TypeScript did win. It's everywhere now, and for good reasons. But let me tell you why, after all these years, I still get a little nostalgic pang when I think about writing CoffeeScript, and why part of me still thinks it was the better language. The Rails Way: Opinionated and Proud First, let's set the scene. This was the golden age of Rails, when "convention over configuration" wasn't just a tagline. It was a philosophy that permeated everything. The asset pipeline handled all your JavaScript and CSS compilation. You'd drop a .coffee file in app/assets/javascripts, write your code, and Rails would handle the rest. No webpack configs, no Babel presets, no decision fatigue about which bundler to use. Your views lived in HAML files that looked like this: .user-profile .header %h1= @user.name %p.bio= @user.bio .actions = link_to "Edit Profile", edit_user_path(@user), class: "btn btn-primary" = link_to "Delete Account", user_path(@user), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-danger" And your JavaScript looked like this: class UserProfile constructor: (@element) -> @setupEventListeners() setupEventListeners: -> @element.find('.btn-danger').on 'click', (e) => @handleDelete(e) handleDelete: (e) -> return unless confirm('Really delete?') $.ajax url: $(e.target).attr('href') method: 'DELETE' success: => @onDeleteSuccess() error: => @onDeleteError() onDeleteSuccess: -> @element.fadeOut() Notifications.show 'Account deleted successfully' $ -> $('.user-profile').each -> new UserProfile($(this)) Look at that. It's beautiful. It's concise. It's expressive. And it just works. Why HAML Was a Breath of Fresh Air Let's talk about HAML first. If you've never used it, HAML (HTML Abstraction Markup Language) was a templating language that let you write HTML without all the angle brackets. Instead of this:

Welcome

This is my website

You wrote this: .container .row .col-md-6 %h1 Welcome %p.lead This is my website The difference is striking. HAML forced you to write clean, properly indented markup. You couldn't forget to close a tag because there were no closing tags. The structure was defined by indentation, Python-style. This meant your templates were always consistently formatted, always readable, and always correctly nested. HAML also integrated beautifully with Ruby. Want to interpolate a variable? Just use =. Want to add a conditional? Use standard Ruby syntax. The mental model was simple: it's just Ruby that outputs HTML. - if current_user.admin? .admin-panel %h2 Admin Controls = render partial: 'admin/controls' - else .user-message %p You don't have access to this section. No context switching between template syntax and programming language syntax. It was all Ruby, all the way down. CoffeeScript: JavaScript for People Who Don't Like JavaScript Now, let's get to the controversial part: CoffeeScript. For those who missed it, CoffeeScript was a language that compiled to JavaScript, created by Jeremy Ashkenas in 2009. It took heavy inspiration from Ruby and Python, offering a cleaner syntax that eliminated much of JavaScript's syntactic noise. Here's the thing people forget: JavaScript in 2011 was terrible. No modules, no classes, no arrow functions, no destructuring, no template strings, no const or let. You had var, function expressions, and pain. So much pain. CoffeeScript gave us: Arrow functions (before ES6): numbers = [1, 2, 3, 4, 5] doubled = numbers.map (n) -> n * 2 Class syntax (before ES6): class Animal constructor: (@name) -> speak: -> console.log "#{@name} makes a sound" class Dog extends Animal speak: -> console.log "#{@name} barks" String interpolation (before ES6): name = "Ivan" greeting = "Hello, #{name}!" Destructuring (before ES6): {name, age} = user [first, second, rest...] = numbers Comprehensions (still not in JavaScript): adults = (person for person in people when person.age >= 18) CoffeeScript didn't just add syntax sugar. It changed how you thought about JavaScript. The code was more expressive, more concise, and more Ruby-like. For Rails developers, it felt like home. The Magic of the Asset Pipeline What made this stack truly shine was how it all fit together. The Rails asset pipeline was like magic. Possibly black magic, but magic nonetheless. You'd organize your code like this: app/assets/ javascripts/ application.coffee models/ user.coffee post.coffee views/ users/ profile.coffee posts/ index.coffee In your application.coffee, you'd require your dependencies: #= require jquery #= require jquery_ujs #= require_tree ./models #= require_tree ./views Rails would automatically compile everything, concatenate it in the right order, minify it for production, and serve it with cache-busting fingerprints. You didn't think about build tools. You just wrote code. The same applied to stylesheets. Drop a .scss file in app/assets/stylesheets, and it would be compiled and served. Want to use a gem that includes assets? Add it to your Gemfile, and its assets would automatically be available. No CDN links, no manual script tags. Was it perfect? No. Was it sometimes confusing when assets weren't loading in the order you expected? Yes. But the developer experience was smooth. You could go from idea to implementation incredibly quickly. Why CoffeeScript Still Feels Better Than TypeScript Okay, here's where I'm going to lose some of you. TypeScript is objectively the right choice for modern JavaScript development. It has type safety, incredible tooling, massive community support, and it's actively developed by Microsoft. CoffeeScript is essentially dead, with minimal updates and a dwindling community. And yet... CoffeeScript still feels better to write. Let me explain. TypeScript added types to JavaScript, which is fantastic. But it kept JavaScript's verbose syntax. You still have curly braces everywhere, you still need semicolons (or don't, which becomes its own debate), you still have visual noise. Compare these: TypeScript: interface User { name: string; email: string; age: number; } class UserService { constructor(private apiClient: ApiClient) {} async getUser(id: string): Promise { const response = await this.apiClient.get(`/users/${id}`); return response.data; } filterAdults(users: User[]): User[] { return users.filter((user) => user.age >= 18); } } CoffeeScript: class UserService constructor: (@apiClient) -> getUser: (id) -> response = await @apiClient.get "/users/#{id}" response.data filterAdults: (users) -> users.filter (user) -> user.age >= 18 The CoffeeScript version is cleaner. There's less visual noise, less ceremony. The @ symbol for instance properties is brilliant. It's immediately obvious what's a property and what's a local variable. The implicit returns mean you're not constantly writing return statements. The significant whitespace enforces good formatting. Yes, TypeScript gives you type safety. That's huge. But CoffeeScript gives you readability. And in my experience, readable code is maintainable code. I can glance at CoffeeScript and immediately understand what it's doing. TypeScript requires more parsing, more mental overhead. The list comprehensions in CoffeeScript are particularly beautiful: # CoffeeScript evenSquares = (n * n for n in numbers when n % 2 == 0) # TypeScript const evenSquares = numbers .filter((n) => n % 2 === 0) .map((n) => n * n); Both work, but the CoffeeScript version reads like English: "n squared for each n in numbers when n is even." It's declarative and expressive. The Existential Operator: A Love Story One of CoffeeScript's best features was the existential operator (?). It was like optional chaining before optional chaining existed, but more powerful: # Safe property access name = user?.profile?.name # Default values speed = options?.speed ? 75 # Function existence check callback?() # Existence assignment value ?= "default" That last one, ?=, was particularly great. It means "assign if the variable is null or undefined." It's cleaner than value = value || "default" and more correct (because it doesn't overwrite falsy-but-valid values like 0 or ""). TypeScript eventually got optional chaining (?.) and nullish coalescing (??), which is great. But it took years, and CoffeeScript had it from the start. What We Lost When the JavaScript community moved from CoffeeScript to ES6 and then TypeScript, we gained a lot. Type safety, better tooling, standardization. But we also lost something. We lost the joy of writing concise code. We lost the elegance of Ruby-inspired syntax. We lost the community that valued readability and expressiveness over completeness and type safety. Modern JavaScript development often feels like you're fighting the tools. Configuring TypeScript, setting up ESLint, configuring Prettier, choosing between competing libraries, debugging sourcemaps, dealing with module resolution issues. It's powerful, but it's exhausting. With CoffeeScript and Rails, you just wrote code. The decisions were made for you. The tools were integrated. The conventions were clear. It was opinionated, and that was a feature, not a bug. The Verdict Would I start a new project with CoffeeScript and HAML today? Probably not. The ecosystem has moved on. TypeScript has won, and for most use cases, it's the right choice. React and Vue have replaced server-rendered templates. The world has changed. But do I miss it? Absolutely. I miss the simplicity. I miss the elegance. I miss being able to write beautiful, concise code without worrying about types and interfaces and generics. I miss HAML's clean markup and the way it forced you to write good HTML. I miss the Rails asset pipeline just working without configuration. Most of all, I miss the developer experience of that era. We were moving fast, building things quickly, and having fun doing it. The code was readable, the stack was coherent, and everything felt like it fit together. Maybe that's just nostalgia talking. Maybe I'm romanticizing the past and forgetting the pain points. But when I look at that old CoffeeScript code, I don't see technical debt. I see craft. I see code that was written with care, that values clarity over cleverness, that respects the reader's time. And honestly? I still think CoffeeScript's syntax is better. TypeScript is more powerful, more practical, and more maintainable at scale. But CoffeeScript is more beautiful. Sometimes, that matters too. What are your thoughts? Did you work with CoffeeScript and HAML back in the day? Do you miss them, or are you glad we've moved on? Let me know in the comments or reach out on Twitter. --- # The Hidden Economics of “Free” AI Tools: Why the SaaS Premium Still Matters URL: https://www.ivanturkovic.com/2025/11/28/the-hidden-economics-of-free-ai-tools-why-the-saas-premium-still-matters/ Published: 2025-11-28 Topics: AI, Career & Leadership, Software Engineering, Startups & Business This is Part 2 of my series on the evolution of SaaS. If you haven't read Part 1: The SaaS Model Isn't Dead, it's Evolving Beyond the Hype of "Vibe Coding", start there for the full context. In this post, I'm diving deeper into the hidden costs that most builders don't see until it's too late. In my last post, I argued that SaaS isn't dead, it's just evolving beyond the surface-level appeal of vibe coding. Today, I want to dig deeper into something most builders don't realize until it's too late: the hidden costs of "free" AI-powered alternatives. Because here's the uncomfortable truth: when you replace a $99/month SaaS tool with a Frankenstein stack of AI prompts, no-code platforms, and API glue, you're not saving money. You're just moving the costs somewhere else, usually to places you can't see until they bite you. Let's talk about what really happens when you choose the "cheaper" path. The Time Tax: When Free Becomes Expensive Picture this: you've built your "MVP" in a weekend. It's glorious. ChatGPT wrote half the code, Zapier connects your Airtable to your Stripe account, and a Make.com scenario handles email notifications. Total monthly cost? Maybe $20 in API fees. You're feeling like a genius. Then Monday morning hits. A customer reports an error. The Zapier workflow failed silently. You spend two hours digging through logs (when you can find them) only to discover that Airtable changed their API rate limits, and now your automation hits them during peak hours. You patch it with a delay. Problem solved. Until Wednesday, when three more edge cases emerge. The Python script you copied from ChatGPT doesn't handle timezone conversions properly. Your payment flow breaks for international customers. The no-code platform you're using doesn't support the webhook format you need. Each fix takes 30 minutes to 3 hours. By Friday, you've spent more time maintaining your "free" stack than you would have spent just using Stripe Billing and ConvertKit. This is the time tax. And unlike your SaaS subscription, you can't expense it or write it off. It's just gone, stolen from building features, talking to customers, or actually running your business. The question isn't whether your DIY solution costs less. It's whether your time is worth $3/hour. The Knowledge Debt: Building on Borrowed Understanding Here's a scenario that plays out constantly in the AI-first era: A developer prompts Claude to build a payment integration. The AI generates beautiful code, type-safe, well-structured, handles edge cases. The developer copies it, tests it once, and ships it. It works perfectly for two months. Then Stripe deprecates an API endpoint. Or a customer discovers a refund edge case. Or the business wants to add subscription tiers. Now what? The developer stares at 200 lines of code they didn't write and don't fully understand. They can prompt the AI again, but they don't know which parts are safe to modify. They don't know why certain patterns were used. They don't know what will break. This is knowledge debt, the accumulated cost of using code you haven't internalized. Compare this to using a proper SaaS tool like Stripe Billing or Chargebee. You don't understand every line of their code either, but you don't need to. They handle the complexity. They migrate your data when APIs change. They've already solved the edge cases. When you build with barely-understood AI-generated code, you get the worst of both worlds: you're responsible for maintenance without having the knowledge to maintain it effectively. This isn't a knock on AI tools. It's a reality check about technical debt in disguise. The Reliability Gap: When "Good Enough" Isn't Let's zoom out and talk about production-grade systems. When you use Slack, it has 99.99% uptime. That's not luck, it's the result of on-call engineers, redundant infrastructure, automated failovers, and millions of dollars in operational excellence. When you stitch together your own "Slack alternative" using Discord webhooks, Airtable, and a Telegram bot, what's your uptime? You don't even know, because you're not measuring it. And here's the thing: your customers notice. They notice when notifications arrive 3 hours late because your Zapier task got queued during peak hours. They notice when your checkout flow breaks because you hit your free-tier API limits. They notice when that one Python script running on Replit randomly stops working. Reliability isn't a feature you can bolt on later. It's the foundation everything else is built on. This is why companies still pay for Datadog instead of writing their own monitoring. Why they use PagerDuty instead of email alerts. Why they choose AWS over running servers in their garage. Not because they can't build these things themselves, but because reliability at scale requires obsessive attention to details that don't show up in MVP prototypes. Your vibe-coded solution might work 95% of the time. But that missing 5% is where trust dies and customers churn. The Support Nightmare: Who Do You Call? Imagine this email from a customer: "Hi, I tried to upgrade my account but got an error. Can you help?" Simple enough, right? Except your "upgrade flow" involves: A Stripe Checkout session (managed by Stripe) A webhook that triggers Make.com (managed by Make.com) Which updates Airtable (managed by Airtable) Which triggers a Zapier workflow (managed by Zapier) Which sends data to your custom API (deployed on Railway) Which updates your database (hosted on PlanetScale) One of these broke. Which one? You have no idea. You start debugging: Check Stripe logs. Payment succeeded. Check Make.com execution logs. Ran successfully. Check Airtable. Record updated. Check Zapier. Task queued but not processed yet. Ah. Zapier's free tier queues tasks during high-traffic periods. The upgrade won't process for another 15 minutes. You explain this to the customer. They're confused and frustrated. So are you. Now imagine that same scenario with a proper SaaS tool like Memberstack or MemberSpace. The customer emails them. They check their logs, identify the issue, and fix it. Done. When you own the entire stack, you own all the problems too. And most founders don't realize how much time "customer support for your custom infrastructure" actually takes until they're drowning in it. The Security Illusion: Compliance Costs You Can't See Pop quiz: Is your AI-generated authentication system GDPR compliant? Does it properly hash passwords? Does it prevent timing attacks? Does it implement proper session management? Does it handle token refresh securely? Does it log security events appropriately? If you're not sure, you've got a problem. Because when you use Auth0, Clerk, or AWS Cognito, these questions are answered for you. They have security teams, penetration testers, and compliance certifications. They handle GDPR, CCPA, SOC2, and whatever acronym-soup regulation applies to your industry. When you roll your own auth with AI-generated code, you own all of that responsibility. And here's what most people don't realize: security incidents are expensive. Not just in terms of fines and legal costs, but in reputation damage and customer trust. One breach can kill a startup. And saying "but ChatGPT wrote the code" isn't a legal defense. The same logic applies to payment handling, data storage, and API security. Every shortcut you take multiplies your risk surface. SaaS tools don't just sell features, they sell peace of mind. They carry the liability so you don't have to. The Scale Wall: When Growth Breaks Everything Your vibe-coded MVP works perfectly for your first 10 customers. Then you get featured on Product Hunt. Suddenly you have 500 new signups in 24 hours. Your Airtable base hits record limits. Your free-tier API quotas are maxed out. Your Make.com scenarios are queuing tasks for hours. Your Railway instance keeps crashing because you didn't configure autoscaling. Your webhook endpoints are timing out because they weren't designed for concurrent requests. Everything is on fire. This is the scale wall, the moment when your clever shortcuts stop being clever and start being catastrophic. Real SaaS products are built to scale. They handle traffic spikes. They have redundancy. They auto-scale infrastructure. They cache aggressively. They optimize database queries. They monitor performance. Your vibe-coded stack probably does none of these things. And here's the brutal part: scaling isn't something you can retrofit easily. It's architectural. You can't just "add more Zapier workflows" your way out of it. At this point, you face a choice: either rebuild everything properly (which takes months and risks losing customers during the transition), or artificially limit your growth to stay within the constraints of your fragile infrastructure. Neither option is appealing. The Integration Trap: When Your Stack Doesn't Play Nice One of the biggest promises of the AI-powered, no-code revolution is that everything integrates with everything. Except it doesn't. Not really. Sure, Zapier connects to 5,000+ apps. But those integrations are surface-level. You get basic CRUD operations, not deep functionality. Want to implement complex business logic? Want custom error handling? Want to batch process data efficiently? Want real-time updates instead of 15-minute polling? Suddenly you're writing custom code anyway, except now you're writing it in the weird constraints of whatever platform you've chosen, rather than in a proper application where you have full control. The irony is thick: you chose no-code to avoid complexity, but you ended up with a different kind of complexity, one that's harder to debug and impossible to version control properly. Meanwhile, a well-designed SaaS tool either handles your use case natively or provides a proper API for custom integration. You're not fighting the platform; you're using it as intended. The Real Cost Comparison Let's do some actual math. Vibe-coded stack: Zapier Pro: $20/month Make.com: $15/month Airtable Pro: $20/month Railway: $10/month Various API costs: $15/month Total: $80/month Your time: Initial setup: 20 hours Weekly maintenance: 3 hours Monthly debugging: 5 hours Customer support for stack issues: 2 hours Monthly time cost: ~20 hours If your time is worth even $50/hour (a modest rate for a technical founder), that's $1,000/month in opportunity cost. Total real cost: $1,080/month. Proper SaaS stack: Stripe Billing: Included with processing fees Memberstack: $25/month ConvertKit: $29/month Vercel: $20/month Total: $74/month + processing fees Your time: Initial setup: 4 hours Weekly maintenance: 0.5 hours Monthly debugging: 1 hour Customer support for stack issues: 0 hours (vendor handles it) Monthly time cost: ~3 hours At $50/hour, that's $150/month in opportunity cost. Total real cost: $224/month. The "more expensive" SaaS stack actually costs 80% less when you account for time. And we haven't even factored in: The revenue lost from downtime The customers lost from poor reliability The scaling issues you'll hit later The security risks you're accepting The knowledge debt you're accumulating When DIY Makes Sense (And When It Doesn't) Look, I'm not saying you should never build anything custom. There are absolutely times when DIY is the right choice. Build custom when: The functionality is core to your competitive advantage No existing tool solves your exact problem You have the expertise to maintain it long-term You're building something genuinely novel You have the team capacity to own it forever Use SaaS when: The functionality is commodity (auth, payments, email, etc.) Reliability and uptime are critical You want to focus on your core product You're a small team with limited time You need compliance and security guarantees You value your time more than monthly fees The pattern is simple: build what makes you unique, buy what makes you functional. The AI-Assisted Middle Ground Here's where it gets interesting: AI doesn't just enable vibe coding. It also enables smarter SaaS integration. You can use Claude or ChatGPT to: Generate integration code for SaaS APIs faster Debug webhook issues more efficiently Build wrapper libraries around vendor SDKs Create custom workflows on top of stable platforms This is the sweet spot: using AI to accelerate your work with reliable tools, rather than using AI to replace reliable tools entirely. Think of it like this: AI is an incredible co-pilot. But you still need the plane to have wings. The Evolution Continues My argument isn't that AI tools are bad or that vibe coding is wrong. It's that we need to be honest about the tradeoffs. The next generation of successful products won't be built by people who reject AI, and they won't be built by people who reject SaaS. They'll be built by people who understand when to use each. People who can vibe-code a prototype in a weekend, then have the discipline to replace it with proper infrastructure before it scales. People who use AI to augment their capabilities, not replace their judgment. The future isn't "AI vs. SaaS." It's "AI-enhanced SaaS." Tools that are easier to integrate because AI helps you. APIs that are easier to understand because AI explains them. Systems that are easier to maintain because AI helps you debug. But beneath all that AI magic, there's still reliable infrastructure, accountable teams, and boring old uptime guarantees. Because at the end of the day, customers don't care about your tech stack. They care that your product works when they need it. Build for the Long Game If you're building something that matters, something you want customers to depend on, something you want to grow into a real business, you need to think beyond the MVP phase. You need to think about what happens when you hit 100 users. Then 1,000. Then 10,000. Will your clever weekend hack still work? Or will you be spending all your time keeping the lights on instead of building new features? The most successful founders I know aren't the ones who move fastest. They're the ones who move sustainably, who build foundations that can support growth without collapsing. They use AI to move faster. They use SaaS to stay reliable. They understand that both are tools, not religions. Final Thoughts: Respect the Craft There's a romance to the idea of building everything yourself. Of being the 10x developer who needs nothing but an AI assistant and pure willpower. But romance doesn't ship products. Discipline does. The best software is invisible. It just works. And making something "just work", consistently, reliably, at scale, is harder than anyone admits. So use AI. Vibe-code your prototypes. Move fast and experiment. But when it's time to ship, when it's time to serve real customers, when it's time to build something that lasts, respect the craft. Choose boring, reliable infrastructure. Pay for the SaaS tools that solve solved problems. Invest in quality over cleverness. Because the goal isn't to build the most innovative tech stack. The goal is to build something customers love and trust. And trust, as it turns out, is built on the boring stuff. The stuff that works when you're not looking. The stuff that scales without breaking. The stuff someone else maintains at 3 AM so you don't have to. That's what SaaS really sells. And that's why it's not dead, it's just getting started. What's your experience balancing custom-built solutions with SaaS tools? Have you hit the scale wall or the reliability gap? Share your stories in the comments. I'd love to hear what you've learned. If you found this useful, follow me for more posts on building sustainable products in the age of AI, where we embrace new tools without forgetting old wisdom. --- # Rails Templating Showdown: Slim vs ERB vs Haml vs Phlex – Which One Should You Use? URL: https://www.ivanturkovic.com/2025/11/26/rails-templating-showdown-slim-vs-erb-vs-haml-vs-phlex-which-one-should-you-use/ Published: 2025-11-26 Topics: Ruby on Rails If you've been working with Ruby on Rails for any length of time, you've probably encountered the age-old question: which templating engine should I use? With ERB as the default, Slim and Haml as popular alternatives, and Phlex as the new kid on the block, the choice can feel overwhelming. In this comprehensive guide, I'll break down each option, compare their strengths and weaknesses, and help you make an informed decision for your Rails projects. Understanding the Landscape Before diving into specifics, let's understand what we're comparing. Template engines are tools that help you generate HTML dynamically by embedding Ruby code within markup. Each engine has a different philosophy about how this should be done. ERB (Embedded Ruby) What is it? ERB is Rails' default templating engine. It embeds Ruby code directly into HTML using special tags. Syntax Example Pros Zero Learning Curve: If you know HTML and Ruby, you already know ERB. There's no new syntax to learn, making it perfect for beginners and mixed teams. Universal Support: Every Rails developer knows ERB. Every gem, tutorial, and Stack Overflow answer uses ERB. This ubiquity is valuable. No Setup Required: It works out of the box with every Rails installation. No gems to add, no configuration needed. Familiar to Other Ecosystems: The concept of embedding code in angle brackets exists in PHP, ASP, JSP, and many other frameworks. Developers coming from other backgrounds will feel at home. Cons Verbose: Writing closing tags for everything gets tedious. Your files become longer than they need to be. Easy to Create Messy Code: Because ERB doesn't enforce structure, it's easy to mix business logic with presentation logic, leading to hard-to-maintain views. Repetitive: You'll find yourself typing the same patterns over and over. The lack of shortcuts makes ERB feel inefficient once you've experienced alternatives. When to Use ERB ERB is ideal when you're starting a new project with junior developers, working with a team that values convention over optimization, or building simple CRUD applications where template complexity is minimal. It's also the safe choice for open-source projects where maximum accessibility matters. Slim What is it? Slim is a lightweight templating engine focused on reducing syntax to its bare essentials. Its motto is "what's left when you take the fat off ERB." Syntax Example .user-profile h1 = @user.name - if @user.admin? span.badge Admin ul.posts - @user.posts.each do |post| li = link_to post.title, post_path(post) Pros Dramatically Less Code: Slim templates are typically 30-40% shorter than their ERB equivalents. This means faster writing and easier scanning. Clean and Readable: Once you learn the syntax, Slim templates are remarkably easy to read. The indentation-based structure naturally enforces good organization. Fast Performance: Slim compiles to Ruby code that's often faster than ERB, though the difference is negligible in most applications. Enforces Good Structure: The indentation requirement prevents messy, unstructured code. You can't create a Slim template that doesn't follow proper nesting. Cons Learning Curve: Team members need to learn new syntax. The first week will involve frequent reference to documentation. Indentation Sensitivity: Like Python, Slim uses significant whitespace. A misplaced space or tab can break your template, which can be frustrating when debugging. Less Common: Fewer developers know Slim compared to ERB. Hiring and onboarding may take slightly longer. Limited Ecosystem Examples: While most gems work fine with Slim, documentation and examples are usually in ERB, requiring mental translation. When to Use Slim Slim shines in applications with complex views where you want to maximize readability and minimize boilerplate. It's perfect for teams that value developer experience and are willing to invest a small amount of time upfront to learn the syntax. If you find yourself frustrated by ERB's verbosity, Slim is your answer. Haml What is it? Haml (HTML Abstraction Markup Language) was one of the first popular alternatives to ERB. It uses indentation to represent HTML structure and eliminates closing tags. Syntax Example .user-profile %h1= @user.name - if @user.admin? %span.badge Admin %ul.posts - @user.posts.each do |post| %li= link_to post.title, post_path(post) Pros Mature and Stable: Haml has been around since 2006. It's battle-tested and reliable with excellent documentation. Cleaner Than ERB: Like Slim, Haml eliminates closing tags and reduces boilerplate significantly. Good Ecosystem Support: Many gems and libraries explicitly support Haml, and you'll find plenty of examples and resources online. Enforces Structure: The indentation requirement keeps your code organized and prevents deeply nested chaos. Cons Slower Than Slim: Haml is noticeably slower than Slim in benchmarks, though for most applications this won't matter. More Verbose Than Slim: The % prefix for tags makes Haml slightly more verbose than Slim's minimalist approach. Indentation Sensitivity: Like Slim, whitespace matters. Mixing tabs and spaces will cause problems. Feeling Dated: While still widely used, Haml hasn't evolved as quickly as Slim. It lacks some of the refinements that make Slim feel more modern. When to Use Haml Choose Haml if you want an alternative to ERB but prefer a more established option with extensive community support. It's a safe middle ground between ERB's verbosity and Slim's minimalism. Haml is particularly good if you're maintaining a legacy codebase that already uses it. Phlex What is it? Phlex represents a radical departure from traditional templating. Instead of mixing Ruby with HTML-like syntax, Phlex uses pure Ruby classes to build views. It's component-oriented and type-safe. Syntax Example class UserProfile < Phlex::HTML def initialize(user) @user = user end def template div(class: "user-profile") do h1 { @user.name } span(class: "badge") { "Admin" } if @user.admin? ul(class: "posts") do @user.posts.each do |post| li { a(href: post_path(post)) { post.title } } end end end end end Pros Pure Ruby: No context switching between Ruby and template syntax. Your entire view is just Ruby code, which means better IDE support, easier refactoring, and familiar debugging. Component Architecture: Phlex encourages building reusable components, leading to better code organization and DRY principles. Type Safety: Because it's pure Ruby, you can use tools like Sorbet or RBS for type checking your views. Excellent Performance: Phlex is extremely fast, often outperforming other template engines significantly. Testable: Components are just Ruby classes, making them easy to unit test without rendering overhead. No Markup Parsing: Since there's no template syntax to parse, there's one less layer of complexity in your stack. Cons Paradigm Shift: Phlex requires a completely different way of thinking about views. This isn't just new syntax—it's a new architecture. Verbose for Simple Views: For basic templates, Phlex can feel like overkill. Writing div { h1 { "Hello" } } instead of

Hello

doesn't feel like progress for simple cases. Limited Ecosystem: Phlex is new. There are fewer examples, fewer ready-made components, and a smaller community. No Designer-Friendly Workflow: Because Phlex is pure Ruby, front-end developers or designers who aren't comfortable with Ruby will struggle to contribute to views. Steep Learning Curve: Understanding how to structure Phlex components well takes time and experience. When to Use Phlex Phlex is ideal for component-heavy applications where you want maximum reusability and testability. It's perfect for design systems, UI libraries, or applications with complex, interactive interfaces. Choose Phlex if your team is comfortable with Ruby and values type safety and performance. It's also excellent for API-driven applications where you're building JSON responses rather than full HTML pages. The Comparison Matrix Let me break down how these engines stack up across key criteria: Performance Winner: Phlex Phlex is the fastest, followed closely by Slim. Haml is slower, and ERB sits in the middle. However, for most applications, template rendering isn't the bottleneck—database queries and business logic are. Readability Winner: Slim Once learned, Slim offers the best balance of conciseness and clarity. ERB is readable but verbose. Haml is good but slightly cluttered with % symbols. Phlex requires Ruby fluency to read comfortably. Learning Curve Winner: ERB ERB has virtually no learning curve. Slim and Haml require a day or two to feel comfortable. Phlex requires rethinking your entire approach to views. Ecosystem Support Winner: ERB ERB is universal. Everything supports it. Slim and Haml have good support but sometimes require translation. Phlex is still building its ecosystem. Maintainability Winner: Phlex/Slim Phlex's component architecture and Slim's enforced structure both lead to highly maintainable codebases. ERB's flexibility can become a maintainability liability. Haml sits in the middle. Team Onboarding Winner: ERB Any Rails developer can contribute to ERB templates immediately. The alternatives require training time. My Recommendations After years of using all these engines in production, here's what I recommend: For New Projects with Small Teams Use Slim. You'll write less code, maintain cleaner views, and the learning investment pays off quickly. The performance gains are nice, but the real benefit is how much easier it is to scan and understand Slim templates. For Large Teams or Open Source Stick with ERB. The universal knowledge and zero onboarding friction outweigh the benefits of alternatives. Don't underestimate the value of every Rails developer being able to contribute immediately. For Component-Heavy Applications Choose Phlex. If you're building a complex UI with lots of reusable components, Phlex's architecture will save you time in the long run. The learning curve is worth it for applications where component composition is central. For Existing Projects Don't Rewrite. If your project already uses Haml or Slim, keep using it. If it uses ERB and you're happy with it, don't change. The cost of conversion rarely justifies the benefits. For Learning Start with ERB, then try Slim. Master Rails with its default templating engine first. Once you're comfortable, experiment with Slim on a side project. After you understand the tradeoffs, you'll be equipped to make informed decisions. Mixing Engines Here's something many developers don't realize: you can use multiple templating engines in the same Rails application. You might use ERB for most views but Phlex for a complex component or Slim for your admin interface. This flexibility means you're not locked into one choice forever. Start with ERB and migrate specific areas to alternatives as needs arise. The Future The Rails templating landscape is evolving. Phlex represents a new wave of thinking about views as components rather than templates. Meanwhile, tools like ViewComponent bridge the gap between traditional templates and component architecture. My prediction? We'll see more hybrid approaches where simple CRUD views use traditional templates while complex UIs leverage component-based systems like Phlex. Conclusion There's no universally correct answer to "which templating engine should I use?" The right choice depends on your team, your project, and your priorities. ERB for maximum compatibility and zero friction Slim for optimal developer experience and clean code Haml for a mature alternative with good ecosystem support Phlex for component-driven architecture and maximum performance My personal preference? I use Slim for most projects. The productivity boost is real, the syntax becomes second nature quickly, and I appreciate how it naturally encourages better code organization. But I've shipped successful applications with all four engines, and I wouldn't hesitate to use any of them given the right context. What matters most isn't which engine you choose, but that you use it consistently and well. A well-structured ERB codebase beats a messy Slim project every time. What's your experience with Rails templating engines? Have you tried alternatives to ERB? I'd love to hear your thoughts in the comments below. Want to dive deeper into Rails development? Subscribe to my newsletter for weekly tips and insights on building better Rails applications. --- # Why AI Startups Should Choose Rails Over Python URL: https://www.ivanturkovic.com/2025/11/20/why-ai-startups-should-choose-rails-over-python/ Published: 2025-11-20 Topics: AI, Ruby on Rails TLDR; Most AI startups fail because they cannot ship a productnot because the model is not good enoughRails gives founders the fastest path from idea to revenuePython is still essential for research but Rails wins when the goal is to build a business. The Real Challenge in AI Today People love talking about modelsbenchmarkstraining runstokenscontext windowsall the shiny parts But none of this is why AI startups fail Startups fail because the supporting layers around the model are too slow to build: Onboarding systems Billing and subscription logic Admin dashboards User management Customer support tools Background processing Iterating on new features Fixing bugs Maintaining stability The model is never the bottleneckThe product isThis is exactly where Rails becomes your unfair advantage Why Rails Gives AI Startups Real Speed Rails focuses on shippingIt gives you a complete system on day oneThe framework removes most of the decisions that slow down small teamsInstead of assembling ten libraries you just start building The result is simpleA solo founder or a tiny team can move with the speed of a full engineering department,Everything feels predictable,Everything fits together,Everything works the moment you touch it. Python gives you freedom,Rails gives you momentum,Momentum is what gets a startup off the ground. Rails and AI Work Together Better Than Most People Think There is a common myth that AI means PythonOnly partially truePython is the best language for training and experimentingBut the moment you are building a feature for real users you need a framework that is designed for production Rails integrates easily with every useful AI service: OpenAI Anthropic Perplexity Groq Nvidia Mistral Any vector database Any embedding store Rails makes AI orchestration simpleSidekiq handles background jobsActive Job gives structureStreaming responses work naturallyYou can build an AI agent inside a Rails app without hacking your way through a forest of scripts The truth is that you do not need Python to run AI in productionYou only need Python if you plan to become a research labMost founders never will Rails Forces You to Think in Systems AI projects built in Python often turn into a stack of disconnected scriptsOne script imports the nextAnother script cleans up dataAnother runs an embedding jobThis continues until even the founder has no idea what the system actually does Rails solves this by designIt introduces structure: Controllers, Services, Models, Jobs, EventsIt forces you to think in terms of a real application rather than a set of experiments This shift is a superpower for foundersAI is moving from research to productionProduction demands structureRails gives you structure without slowing you down Why Solo Founders Thrive With Rails When you are building alone you cannot afford chaosYou need to create a system that your future self can maintainRails gives you everything that normally requires a team You can add authentication in a few minutesYou can build a clean admin interface in a single afternoonYou can create background workflows without debugging weird timeoutsYou can send emails without configuring a jungle of librariesYou can go from idea to working feature in the same day This is what every founder actually needsNot experimentsNot scriptsA product that feels realA product you can ship this weekA product you can charge money for Rails gives you that reality Real Companies That Prove Rails Is Still a Winning Choice Rails is not a nostalgia frameworkIt is the foundation behind some of the biggest products ever created GitHub started with RailsShopify grew massive on RailsAirbnb used Rails in its early explosive phaseHuluZendeskBasecampDribbbleAll Rails Modern AI driven companies also use Rails in productionShopify uses it to power AI commerce featuresIntercom uses it to support AI customer support workflowsGitHub still relies on Rails for internal systems even as it builds CopilotStripe uses Rails for internal tools because the Python stack is too slow for building complex dashboards These are not lightweight toy projectsThese are serious companies that trust Rails because it just works What You Gain When You Choose Rails The biggest advantage is development speedNot theoretical speedReal speedThe kind that lets you finish an entire feature before dinner SecondYou escape the burden of endless decisionsThe framework already gives you the right defaultsYou do not waste time choosing from twenty possible libraries for each part of the system ThirdRails was built for productionThis matters more than people admitYou get caching, background jobs, templates, email, tests, routing, security, all included, all consistent, all reliable FourthRails fits perfectly with modern AI infrastructure: Vector stores, embedding workflows, agent orchestration, streaming responses. It works out of the box with almost no friction This combination is rareRails gives you speed and stability at the same timeMost frameworks give you one or the otherRails gives you both Where Rails Is Not the Best Too There are honest limits. If you are training models working with massive research datasets, writing CUDA kernels or doing deep ML research Python remains the right choice. If you come from Python the Rails conventions can feel magical or strange at first You might wonder why things happen automatically. But the conventions are there to help you move faster Hiring can be more challenging in certain regionsThere are fewer Rails developersbut the ones you find are usually very strongand often much more experienced in building actual products You might also deal with some bias. A few people still assume Rails is oldThese people are usually too young to remember that Rails built half the modern internet The One Thing Every Founder Must Understand The future of AI will not be won by better models. Models are quickly becoming a commodity. The real victory will go to the teams that build the best products around the models: Onboarding UX speed reliability iteration support support tools customer insights monetization all the invisible details that turn a clever idea into a real business Rails is the best framework in the world for building these supporting layers fast. This is why it remains one of the most effective choices for early stage AI startups Use Python for researchUse Rails to build the businessThis is the strategy that gives you the highest chance of reaching customersand more importantlythe highest chance of winning --- # The AI-Native Rails App: What a 2025 Architecture Looks Like URL: https://www.ivanturkovic.com/2025/11/19/the-ai-native-rails-app-what-a-2025-architecture-looks-like/ Published: 2025-11-19 Topics: Ruby on Rails Introduction For the first time in decades of building products, I’m seeing a shift that feels bigger than mobile or cloud.AI-native architecture isn’t “AI added into the app” it’s the app shaped around AI from day one. In this new world: Rails is no longer the main intelligence layer Rails becomes the orchestrator The AI systems do the thinking The Rails app enforces structure, rules, and grounding And honestly? Rails has never felt more relevant than in 2025. In this post, I’m breaking down exactly what an AI-native Rails architecture looks like today, why it matters, and how to build it with real, founder-level examples from practical product work. 1. AI-Native Rails vs. AI-Powered Rails Many apps today use AI like this: User enters text → you send it to OpenAI → you show the result That’s not AI-native.That’s “LLM glued onto a CRUD app.” AI-native means: Your DB supports vector search Your UI expects streaming Your workflows assume LLM latency Your logic expects probabilistic answers Your system orchestrates multi-step reasoning Your workers coordinate long-running tasks Your app is built around contextual knowledge, not just forms A 2025 AI-native Rails stack looks like this: Rails 7/8 Hotwire (Turbo + Stimulus) Sidekiq or Solid Queue Postgres with PgVector OpenAI, Anthropic, or Groq APIs Langchain.rb for tooling and structure ActionCable for token-by-token streaming Comprehensive logging and observability This is the difference between a toy and a business. 2. Rails as the AI Orchestrator AI-native architecture can be summarized in one sentence: Rails handles the constraints, AI handles the uncertainty. Rails does: validation data retrieval vector search chain orchestration rule enforcement tool routing background workflows streaming to UI cost tracking The AI does: reasoning summarization problem-solving planning generating drafts interpreting ambiguous input In an AI-native system: Rails is the conductor. The AI is the orchestra. 3. Real Example: AI Customer Support for Ecommerce Most ecommerce AI support systems are fragile: they hallucinate answers they guess policies they misquote data they forget context An AI-native Rails solution works very differently. Step 1: User submits a question A Turbo Frame or Turbo Stream posts to: POST /support_queries Rails saves: user question metadata Step 2: Rails triggers two workers (1) EmbeddingJob– Create embeddings via OpenAI– Save vector into PgVector column (2) AnswerGenerationJob– Perform similarity search on: product catalog order history return policies previous chats FAQ rules– Pass retrieved context into LLM– Validate JSON output– Store reasoning steps (optional) Step 3: Stream the answer ActionCable + Turbo Streams push tokens as they arrive. broadcast_append_to "support_chat_#{id}" The user sees the answer appear live, like a human typing. Why this architecture matters for founders Accuracy skyrockets with grounding Cost drops because vector search reduces tokens Hallucinations fall due to enforced structure You can audit the exact context used UX improves dramatically with streaming Support cost decreases 50–70% in real deployments This isn’t AI chat inside Rails. This is AI replacing Tier-1 support, with Rails as the backbone of the system. 4. Example: Founder Tools for Strategy, Decks, and Roadmaps Imagine building a platform where founders upload: pitch decks PDFs investor emails spreadsheets competitor research user feedback product specs Old SaaS approach:You let GPT speculate. AI-native approach:You let GPT reason using real company documents. How it works Step 1: Upload documents Rails converts PDFs → text → chunks → embeddings. Step 2: Store a knowledge graph PgVector stores embeddings.Metadata connects insights. Step 3: Rails defines structure Rails enforces: schemas output formats business rules agent constraints allowed tools validation filters Step 4: Langchain.rb orchestrates the reasoning But Rails sets the boundaries.The AI stays inside the rails (pun intended). Step 5: Turbo Streams show ongoing progress Founders see: “Extracting insights…” “Analyzing competitors…” “Summarizing risks…” “Drafting roadmap…” This builds trust and increases perceived value. 5. Technical Breakdown: What You Need to Build Below is the exact architecture I recommend. 1. Rails + Hotwire Frontend Turbo Streams = real-time AI experience. Streams for token output Frames for async updates No need for React overhead 2. PgVector for AI Memory Install extension + migration. Example schema: create_table :documents do |t| t.text :content t.vector :embedding, limit: 1536 t.timestamps end Vectors become queryable like any column. 3. Sidekiq or Solid Queue for AI Orchestration LLM calls must never run in controllers. Recommended jobs: EmbeddingJob ChunkingJob RetrievalJob LLMQueryJob GroundedAnswerJob AgentWorkflowJob 4. AI Services Layer Lightweight Ruby service objects. Embedding example: class Embeddings::Create def call(text) OpenAI::Client.new.embeddings( model: "text-embedding-3-large", input: text )["data"][0]["embedding"] end end 5. Retrieval Layer Document.order(Arel.sql("embedding <-> '#{embedding}' ASC")).limit(5) Grounding prevents hallucinations and cuts costs. 6. Streaming with ActionCable Token streaming UX looks magical and retains users. 7. Observability Layer (Non-Optional) Track: prompts model cost context chunks errors retries latency AI systems break differently than traditional code.Logging is survival. 6. How To Start Building This (Exact Steps) Here’s the fast-track setup: Step 1: Enable PgVector Install and migrate. Step 2: Build an Embedding Service Clean, testable, pure Ruby. Step 3: Add Worker Pipeline One worker per step.No logic inside controllers. Step 4: Create Retrieval Functions Structured context retrieval before every LLM call. Step 5: Build Token Streaming Turbo Streams + ActionCable. Step 6: Add Prompt Templates & A/B Testing Prompt engineering is your new growth lever. 7. Why Rails Wins the AI Era AI products are: async slow streaming-heavy stateful data-driven orchestration heavy context dependent Rails was made for this style of work. Python builds models.Rails builds businesses. We are entering an era where: Rails becomes the best framework in the world for shipping AI-powered products fast. And I’m betting on it again like I did 15 years ago but with even more conviction. Closing Thoughts Your product is no longer a set of forms.In the AI era, your product is: memory context retrieval reasoning workflows streaming interfaces orchestration Rails is the perfect orchestrator for all of it. --- # The Two Hardest Problems in Software Development: Naming Things & Cache Invalidation URL: https://www.ivanturkovic.com/2025/11/18/the-two-hardest-problems-in-software-development-naming-things-cache-invalidation/ Published: 2025-11-18 Topics: AI, Ruby on Rails A joke. A reality. A shared developer trauma now with AI and Ruby flavor. Every industry has its running jokes. Lawyers have billable hours. Doctors have unreadable handwriting. Accountants battle ancient spreadsheets. Developers?We have two immortal bosses at the end of every level: 1. Naming things2. Cache invalidation These aren’t just memes.They’re universal rites of passage, the kind of problems that don’t care about your stack, your years of experience, or your productivity plans for the day. And as modern as our tools get, these two battles remain undefeated. 1. Naming Things: A Daily Existential Crisis You would think building multi-region distributed systems or designing production-grade blockchains is harder than deciding how to name a method. But no.Naming is where confidence goes to die. There’s something profoundly humbling about: Typing data, deleting it, typing result, deleting it, typing payload, staring at it, deleting it again, then settling on something like final_sanitized_output and hoping future-you understands the intention. Naming = Thinking A name isn’t just a word.It’s a miniature problem statement. A good name answers: What is this? Why does it exist? What is it supposed to do? Is it allowed to change? Should anyone else touch it? A bad name answers none of that but invites everyone on your team to ping you on Slack at 22:00 asking “hey what does temp2 mean?” Not being a native English speaker? Welcome to the Hard Mode DLC For those of us whose brain grew up in Croatian or Slovenian, naming in English is a special kind of fun. You might know exactly what you want to say in your own language, but English gives you: three misleading synonyms, one obscure word nobody uses, and a fourth option that feels right but actually means “a small marine snail.” Sometimes you choose a word that sounds good.Later a native speaker reviews it and politely suggests:“Did you mean something else?” Yes.I meant something else.I just didn’t know the word. And every developer from Europe, Asia, or South America collectively understands this pain. 2. Cache Invalidation: “Why is my app showing data from last week?” Caching seems easy on paper: Save expensive data → Serve it fast → Refresh it when needed. Unfortunately, “when needed” is where the nightmares begin. Cache invalidation is unpredictable because it lives at the intersection of: time state concurrency user behavior background jobs frameworks deployment pipelines the moon cycle your personal sins You delete the key.The stale value still appears.You restart the server.It refuses to die. You clear your browser cache.Nothing changes. Then you realize:Ah. It’s Cloudflare.Or Redis.Or Rails fragment caching.Or your CDN.Or… you know what, it doesn’t matter anymore. You’re in too deep. 3. “But can’t AI fix it?” Not… really.Not even close. Large language models can: produce suggestions, generate name variations, summarize logic, help brainstorm alternative wording. But they don’t actually understand your domain, your codebase, your long-term architecture, or your internal conventions. Their naming suggestions are based on statistical patterns in text not on: business logic your future plans subtle behavior differences what will still make sense in six months what your teammates expect what your product owner actually wants AI might suggest a “good enough” name that reads nicely,but it won’t know that half your system expects a value to mean something slightly different, or that “order” conflicts with another concept named “Order” in a separate context. And with cache invalidation?AI can generate explanations but it can’t magically deduce your system’s lifetime, caching layers, or deployment quirks.It cannot predict race conditions or magically detect all the hidden layers where stale data might be hiding like a gremlin. AI helps you write code faster.But it does not remove the need for deep understanding, consistent thinking, and human judgment. 4. How Ruby Tries to Save Us From Ourselves Ruby and Ruby on Rails in particular has spent two decades trying to soften the blow of both naming and system complexity. Not by solving the problem completely, but by making the playground safer. Ruby’s Naming Conventions = Guardrails for Humans Ruby tries to push developers toward sanity through: clear method naming idioms (predicate?, bang!, _before_type_cast) consistent pluralization rules convention-driven file names and classes ActiveRecord naming patterns like User → users, Person → people Rails developers don’t choose how to name directories, controllers, helpers, or models.Rails chooses for you. This is not a limitation it’s freedom. The fewer decisions you have to make about structure, the more mental energy you save for meaningful names, not framework boilerplate. Ruby Reduces the “Naming Chaos Budget” Thanks to convention-over-configuration: folders behave predictably classes match filenames methods follow community patterns model names map directly to database entities you don’t spend half your day wondering where things live Ruby doesn’t fix naming.It simply reduces the size of the battlefield. Ruby Also Softens Caching Pain… a Bit Rails gives you: fragment caching Russian doll caching cache keys with versioning (cache_key_with_version) automatic key invalidation via ActiveRecord touch expiry helpers (expires_in, expires_at) per-request cache stores caching tied to view rendering Rails tries to help you avoid stale data by structuring caching around data freshness instead of low-level keys. But even then… The moment you have: multiple services, background jobs, external APIs, or anything distributed… Ruby smiles kindly and whispers:“You’re on your own now, my friend.” Why These Problems Never Disappear Because they aren’t technical problems.They are human constraints on top of technical systems. Naming requires clarity of thinking. Caching requires clarity of system behavior. AI can assist, but it cannot replace understanding. Ruby can guide you, but it cannot decide for you. The tools help.The frameworks help.AI helps. But at the end of the day, the two hardest problems remain hard because they require the one thing no machine or framework can automate: your own reasoning. Final Thoughts Some days you ship incredible features.Some days you wage war against a variable name.Some days you fight stale cached data for three hours before realizing the problem was a CDN rule from 2018. And every developer, everywhere on Earth, understands these moments. Naming things and cache invalidation aren’t just computer science problems.They’re reminders of why software development is deeply human full of ambiguity, creativity, and shared misery. But honestly? That’s what keeps it fun. --- # PgVector for AI Memory in Production Applications URL: https://www.ivanturkovic.com/2025/11/16/pgvector-for-ai-memory-in-production-applications/ Published: 2025-11-16 Topics: AI, Career & Leadership Introduction As AI moves from experimentation into real products, one challenge appears over and over again: memory. Large language models (LLMs) are incredibly capable, but they can’t store long-term knowledge about users or applications out-of-the-box. They respond only to what they see in the prompt and once the prompt ends, the memory disappears. This is where vector databases and especially PgVector step in. PgVector is a PostgreSQL extension that adds first-class vector similarity search to a database you probably already use. With its rise in popularity especially in production AI systems it has become one of the simplest and most powerful ways to build AI memory. This post is a deep dive into PgVector, how it works, why it matters, and how to implement it properly for real LLM-powered features. What Is PgVector? PgVector is an open-source PostgreSQL extension that adds support for storing and querying vector data types. These vectors represent high‑dimensional numerical representations embeddings generated from AI models. Examples: A sentence embedding from OpenAI might be a vector of 1,536 floating‑point numbers. An image embedding from CLIP might be 512 or 768 numbers. A user profile embedding might be custom‑generated from your own model. PgVector lets you: Store these vectors Index them efficiently Query them using similarity search (cosine, inner product, Euclidean) This enables your LLM applications to: Retrieve knowledge Add persistent memory Reduce hallucinations Add personalization or context Build recommendation engines And all of that without adding a new complex piece of infrastructure because it works inside PostgreSQL. How PgVector Works At its core, PgVector introduces a new column type: vector(1536) You decide the dimension based on your embedding model. PgVector then stores the vector and allows efficient search using: Cosine distance (1 - cosine similarity) Inner product Euclidean (L2) Similarity Search Similarity search means: given an embedding vector, find the stored vectors that are closest to it. This is crucial for LLM memory. Instead of asking the model to “remember” everything or hallucinating answers, we retrieve the most relevant facts, messages, documents, or prior interactions before the LLM generates a response. Indexing PgVector supports two main index types: IVFFlat (fast, approximate search – great for production) HNSW (hierarchical – even faster for large datasets) Example index creation: CREATE INDEX ON memories USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); Using PgVector With Embeddings Step 1: Generate Embeddings You generate embeddings from any model: OpenAI Embeddings Azure HuggingFace models Cohere Llama.cpp Custom fine‑tuned transformers Example (OpenAI): POST https://api.openai.com/v1/embeddings {"model": "text-embedding-3-large","input": "Hello world"} This returns a vector like: [0.0213, -0.0045, 0.9983, ...] Step 2: Store Embeddings in PostgreSQL A table for memory might look like: CREATE TABLE memory (id SERIAL PRIMARY KEY, content TEXT NOT NULL, embedding vector(1536), metadata JSONB, created_at TIMESTAMP DEFAULT NOW()); Insert data: INSERT INTO memory (content, embedding) VALUES ('User likes Japanese and Mexican cuisine', '[0.234, -0.998, ...]'); Step 3: Query Similar Records SELECT content, (embedding <=> '[0.23, -0.99, ...]') AS distance FROM memory ORDER BY embedding <=> '[0.23, -0.99, ...]' LIMIT 5; This returns the top 5 most relevant memory snippets and those will be added to the prompt context. Storing Values for AI Memory What You Store Depends on Your Application You can store: Chat history messages User preferences Past actions Product details Documents Errors and solutions Knowledge base articles User profiles Recommended Structure A flexible structure: { "type": "preference", "user_id": 42, "source": "chat", "topic": "food", "tags": ["japanese", "mexican"] } This gives you the ability to: Filter search by metadata Separate memories per user Restrict context retrieval by type Temporal Decay (Optional) You can implement ranking adjustments: Recent memories score higher Irrelevant memories score lower Outdated memories auto‑expire This creates human‑like memory behavior. Reducing Hallucinations With PgVector LLMs hallucinate when they lack context. Most hallucinations are caused by missing information, not by model failure. PgVector solves this by ensuring the model always receives: The top relevant facts Accurate summaries Verified data Retrieval-Augmented Generation (RAG) You transform a prompt from: Without RAG: "Tell me about Ivan's garden in Canada." With RAG: "Tell me about Ivan's garden in Canada. Here are relevant facts from memory: The garden is 20m². - Located in Canada. - Used for planting vegetables." The model no longer needs to guess. Why This Reduces Hallucination Because the model: Is not guessing user data Only completes based on retrieved facts Gets guardrails through data-driven knowledge Becomes deterministic PgVector acts like a mental database for the AI. Adding PgVector to a Production App Here’s the blueprint. 1. Install the extension CREATE EXTENSION IF NOT EXISTS vector; 2. Create your memory table Use the structure that fits your domain. 3. Create an index CREATE INDEX memory_embedding_idx ON memory USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); 4. Create a Memory Service Your backend service should: Accept content Generate embeddings Store them with metadata And another service should: Take an embedding Query top-N matches Return the context 5. Use RAG in your LLM pipeline Every LLM call becomes: Embed the question Retrieve relevant memory Construct prompt Call the LLM Store new memories (if needed) 6. Add Guardrails Production memory systems need: Permission control (per user) Expiration rules Filters (e.g., exclude private data) Maximum memory size 7. Add Analytics Track: Hit rate (how often memory is used) Relevance quality Retrieval time Common Pitfalls and How to Avoid Them ❌ Storing whole conversation transcripts This leads to massive token usage. Instead, store summaries. ❌ Retrieving too many memories Keep context small. 3–10 items is ideal. ❌ Wrong distance metric Most embedding models work best with cosine similarity. ❌ Using RAG without metadata filters You don’t want another user’s memory leaking into the context. ❌ No indexing Without IVFFlat/HNSW, retrieval becomes extremely slow. When Should You Use PgVector? Use it if you: Already use PostgreSQL Want simple deployment Want memory that scales to millions of rows Need reliability and ACID guarantees Want to avoid new infrastructure like Pinecone, Weaviate, or Milvus Do NOT use it if you: Need billion‑scale vector search Require ultra‑low latency for real‑time gaming or streaming Need dynamic sharding across many nodes But for 95% of AI apps, PgVector is perfect. Conclusion PgVector is the bridge between normal production data and the emerging world of AI memory. For developers building real applications chatbots, agents, assistants, search engines, personalization engines it offers the most convenient and stable foundation. You get: Easy deployment Reliable storage Fast similarity search A complete memory layer for AI This turns your LLM features from fragile experiments into solid, predictable production systems. If you're building AI products in 2025, PgVector isn’t "nice to have" it’s a core architectural component. --- # Saving Money With Embeddings in AI Memory Systems: Why Ruby on Rails is Perfect for LangChain URL: https://www.ivanturkovic.com/2025/11/14/saving-money-with-embeddings-in-ai-memory-systems-why-ruby-on-rails-is-perfect-for-langchain/ Published: 2025-11-14 Topics: AI, Ruby on Rails, Software Engineering Over the last few months of rebuilding my Rails muscle memory, I’ve been diving deep into AI memory systems and experimenting with embeddings. One of the biggest lessons I’ve learned is that the cost of building AI isn’t just in the model it’s in how you use it. Tokens, storage, retrieval these are the hidden levers that determine whether your AI stack remains elegant or becomes a runaway expense. And here’s the good news: with Ruby on Rails, managing these complexities becomes remarkably simple. Rails has always been about turning complicated things into something intuitive and maintainable and when you pair it with LangChain, it feels like magic. Understanding the Cost of Embeddings Most people think that running large language models is expensive because of the model itself. That’s only partially true. In practice, the real costs come from: Storing too much raw content: Every extra paragraph you embed costs more in tokens, both for the embedding itself and for later retrieval. Embedding long texts instead of summaries: LLMs don’t need the full novel they often just need the distilled version. Summaries are shorter, cheaper, and surprisingly effective. Retrieving too many memories: Pulling 50 memories for a simple question can cost more than the model call itself. Smart retrieval strategies can drastically cut costs. Feeding oversized prompts into the model: Every extra token in your prompt adds up. Cleaner prompts = cheaper calls. I’ve seen projects where embedding every word of a document seemed “safe,” only to realize months later that the token bills were astronomical. That’s when I started thinking in terms of summary-first embeddings. How Ruby on Rails Makes It Easy Rails is my natural playground for building systems that scale reliably without over-engineering. Why does Rails pair so well with AI memory systems and LangChain? Several reasons: Migrations Are ElegantWith Rails, adding a vector column with PgVector feels like any other migration. You can define your tables, indexes, and limits in one concise block: class AddMemoriesTable < ActiveRecord::Migration[7.1] def change enable_extension "vector" create_table :memories do |t| t.text :content, null: false t.vector :embedding, limit: 1536 t.jsonb :metadata t.timestamps end end end There’s no need for complicated schema scripts. Rails handles the boring but essential details for you. ActiveRecord Makes Embedding Storage a BreezeStoring embeddings in Rails is almost poetic. With a simple model, you can create a memory with content, an embedding, and metadata in a single call: Memory.create!( content: "User prefers Japanese and Mexican cuisine.", embedding: embedding_vector, metadata: { type: :preference, user_id: 42 } ) And yes, you can query those memories by similarity in a single, readable line: Memory.order(Arel.sql("embedding <=> '[#{query_embedding.join(',')}]'")).limit(5) Rails keeps your code readable and maintainable while you handle sophisticated vector queries. LangChain Integration is NaturalLangChain is all about chaining LLM calls, memory storage, and retrieval. In Rails, you already have everything you need: models, services, and job queues. You can plug LangChain into your Rails services to: Saving Money with Smart Embeddings Here’s the approach I’ve refined over multiple projects: Summarize Before You EmbedInstead of embedding full documents, feed the model a summary. A 50-word summary costs fewer tokens but preserves the semantic meaning needed for retrieval. Limit Memory RetrievalYou rarely need more than 5–10 memories for a single model call. More often than not, extra memories just bloat your prompt and inflate costs. Use Metadata WiselyStore small, structured metadata alongside your embeddings to filter memories before similarity search. For example, filter by user_id or type instead of pulling all records into the model. Cache StrategicallyDon’t re-embed unchanged content. Use Rails validations, background jobs, and services to embed only when necessary. When you combine these strategies, the savings are significant. In some projects, embedding costs dropped by over 70% without losing retrieval accuracy. Why I Stick With Rails and PostgreSQL There are many ways to build AI memory systems. You could go with specialized databases, microservices, or cloud vector stores. But here’s what keeps me on Rails and Postgres: Reliability: Postgres is mature, stable, and production-ready. PgVector adds vector search without changing the foundation. Scalability: Rails scales surprisingly well when you keep queries efficient and leverage background jobs. Developer Happiness: Rails lets me iterate quickly. I can prototype, test, and deploy AI memory features without feeling like I’m juggling ten different systems. Future-Proofing: Rails projects can last years without a complete rewrite. AI infrastructure is still evolving having a stable base matters. Closing Thoughts AI memory doesn’t have to be complicated or expensive. By thinking carefully about embeddings, summaries, retrieval, and token usage and by leveraging Rails with LangChain you can build memory systems that are elegant, fast, and cost-effective. For me, Rails is more than a framework. It’s a philosophy: build systems that scale naturally, make code readable, and keep complexity under control. Add PgVector and LangChain to that mix, and suddenly AI memory feels like something you can build without compromise. In the world of AI, where complexity grows faster than budgets, that kind of simplicity is priceless. --- # The SaaS Model Isn’t Dead, it’s Evolving Beyond the Hype of “Vibe Coding” URL: https://www.ivanturkovic.com/2025/11/11/the-saas-model-isnt-dead-its-evolving-beyond-the-hype-of-vibe-coding/ Published: 2025-11-11 Topics: AI, Career & Leadership, Software Engineering, Startups & Business “The SaaS model is dead. Long live vibe-coded AI scripts.” That’s the kind of hot take lighting up LinkedIn half ironic, half prophetic. Why pay $99/month for a product when you can stitch together 12 AI prompts, 3 no-code hacks, and a duct-taped Python script you barely understand? Welcome to vibe coding. It feels fast. It feels clever.Until the vibes break and no one knows why. The Mirage of Instant Software We live in an era of speed.AI gives us instant answers, mockups, and even “apps.” The line between prototype and product has never been thinner and that’s both empowering and dangerous. What used to take months of product design, testing, and iteration can now be faked in a weekend.You can prompt ChatGPT to generate a working landing page, use Bubble or Replit for logic, and Zapier to glue it all together. Boom “launch” your MVP. But here’s the truth no one wants to say out loud:Most of these AI-fueled prototypes aren’t MVPs. They’re demos with good lighting. A real MVP isn’t about how fast you can ship; it’s about how reliably you can learn from what you ship. And learning requires stability.You can’t measure churn or retention when your backend breaks every other day.You can’t build trust when your app crashes under 20 users. That’s when the vibes start to fade. The Boring Truth Behind Great Products Let’s talk about what SaaS really sells.It’s not just the product you see it’s everything beneath it: Uptime: Someone is on-call at 3 AM keeping your app alive. Security: Encryption, audits, GDPR, SOC2 the invisible scaffolding of trust. Maintenance: When APIs change or libraries break, someone fixes it. Versioning: “Update Available” didn’t write itself. Support: Human beings who care when you open a ticket. When you pay for SaaS, you’re not paying for buttons.You’re paying for accountability for the guarantee that someone else handles the boring stuff while you focus on your business. And boring, in software, is beautiful.Because it means stability. Predictability. Peace of mind. The Myth of the One-Prompt MVP There’s a growing illusion that AI can replace the entire MVP process.Just write a long enough prompt, and out comes your startup. Except… no. Building an MVP is not about output. It’s about the iteration loop testing, learning, refining. A real MVP requires: Instrumentation: Analytics to track usage and retention. UX Design: Understanding user friction. Scalability: Handling 500 users without collapse. Product Roadmap: Knowing what not to build yet. Legal & Compliance: Because privacy questions always come. AI can accelerate this process but it can’t replace it.Because AI doesn’t understand your market context, users, or business model.It’s a tool not a cofounder. From Vibes to Viability There’s real power in AI-assisted building.You can move fast, experiment, and prototype ideas cheaply. But once something works, you’ll need to replace your prompt stack and Zapier web of glue code with solid infrastructure. That’s when the SaaS mindset returns.Not because you need to “go old school,” but because you need to go sustainable. Every successful product eventually faces the same questions: Who maintains this? Who owns the data? Who ensures it still works next month? Who’s responsible when it breaks? The answer, in true SaaS fashion, must always be: someone accountable. SaaS Isn’t Dead, it’s Maturing The world doesn’t need more quick hacks.It needs more craftsmanship builders who blend speed with discipline, creativity with structure, and vibes with reliability. SaaS isn’t dying; it’s evolving. Tomorrow’s SaaS might not look like subscription dashboards.It might look like AI agents, private APIs, or personalized data layers. But behind every “smart” layer will still be boring, dependable infrastructure databases, authentication, servers, and teams maintaining uptime. The form changes.The value reliability, scalability, trust never does. Final Thought: Build With Vibes, Ship With Discipline There’s nothing wrong with vibe coding. It’s an amazing way to experiment and learn. But if you want to launch something that lasts, something customers depend on you’ll need more than vibes.You’ll need product thinking, process, and patience. That’s what separates a weekend project from a real business. So build with vibes.But ship with discipline. Because that’s where the magic and the money really happens. If you liked this post, follow me for more thoughts on building real products in the age of AI hype where craftsmanship beats shortcuts every time. --- # Artisanal Coding (職人コーディング): A Manifesto for the Next Era of Software Craftsmanship URL: https://www.ivanturkovic.com/2025/10/30/artesanal-coding-%e8%81%b7%e4%ba%ba%e3%82%b3%e3%83%bc%e3%83%87%e3%82%a3%e3%83%b3%e3%82%b0-a-manifesto-for-the-next-era-of-software-craftsmanship/ Published: 2025-10-30 Topics: AI, Software Engineering In an age where code seems to write itself and AI promises to make every developer “10x faster,” something essential has quietly started to erode our craftsmanship. I call the counter-movement to this erosion artisanal coding. Like artisanal bread or craft coffee, artisanal coding is not about nostalgia or resistance to progress. It’s about intentionality, quality, and soul; things that can’t be automated, templated, or generated in bulk. It’s the human touch in a field that’s rushing to outsource its own intuition. What Is Artisanal Coding (職人コーディング)? Artisanal coding is the conscious resistance to that decay.It’s not anti-AI, it’s anti-carelessness. It’s the belief that the best code is still handmade, understood, and cared for. Think of an artisan carpenter.He can use power tools but he knows when to stop and sand by hand. He knows the wood, feels its resistance, and adjusts. He doesn’t mass-produce he perfects. Artisanal coding applies that mindset to software. It’s about: Understanding the problem before touching the code. Writing it line by line, consciously. Refactoring not because a tool says so, but because you feel the imbalance. Learning from your errors instead of patching them away. It’s slow. It’s deliberate. And that’s the point. Artisanal coding is the deliberate act of writing software by hand, with care, precision, and understanding. It’s the opposite of what I call vibe coding the growing trend of throwing AI-generated snippets together, guided by vibes and autocomplete rather than comprehension. This is not about rejecting tools it’s about rejecting the loss of mastery. It’s a mindset that values the slow process of creation, the small victories of debugging, and the satisfaction of knowing your code’s structure like a craftsman knows the grain of wood. Why We Need Artisanal Coding We’re losing our muscle memory.Developers who rely too heavily on AI are forgetting how to solve problems from first principles. Code completion is helpful, but when it replaces thought, the skill atrophies. Code quality is declining behind pretty demos.Vibe coding produces software that “works” today but collapses tomorrow. Without deep understanding, we can’t reason about edge cases, performance, or scalability. We risk becoming code operators instead of creators.The satisfaction of crafting something elegant is replaced by prompt-tweaking and debugging alien code. Artisanal coding restores that connection between creator and creation. AI cannot feel the friction.Friction is good. The process of struggling through a bug teaches lessons that no autocomplete can. That frustration is where true craftsmanship is born. The Role (and Limitations) of AI in Artisanal Coding Artisanal coding doesn’t ban AI. It just defines healthy boundaries for its use. ✅ Allowed AI usage: Short code completions: Using AI to fill in a few lines of boilerplate or repetitive syntax. Troubleshooting assistance: Asking AI or community-like queries outside the codebase similar to how you’d ask Stack Overflow or a mentor for advice. 🚫 Not allowed: Generating entire functions or components without understanding them. Using AI to “design” the logic of your app. Copy-pasting large sections of unverified code. AI can be your assistant, not your replacement. Think of it as a digital apprentice, not a co-author. The Future Depends on How We Code Now As we rush toward AI-assisted everything, we risk raising a generation of developers who can’t code without help. Artisanal coding is a statement of independence a call to slow down, think deeply, and keep your hands on the keyboard with intent. Just as artisans revived craftsmanship in industries overtaken by automation, we can do the same in tech. The software we write today shapes the world we live in tomorrow. It deserves the same care as any other craft. Artisanal coding is not a movement of the past, it’s a movement for the future.Because even in the age of AI, quality still matters. Understanding still matters. Humans still matter. If vibe coding is the fast food of software, artisanal coding is the slow-cooked meal: nourishing, deliberate, and made with care.It takes more time, yes. But it’s worth every second. Let’s bring back pride to our craft.Let’s code like artisans again. In many ways, artisanal coding echoes the Japanese philosophies of Shokunin [職人] (the pursuit of mastery through mindful repetition), Wabi-sabi [侘寂] (the acceptance of imperfection as beauty), and Kaizen [改善] (the quiet dedication to constant improvement). A true craftsperson doesn’t rush; they refine. They don’t chase perfection; they respect the process. Coding, like Japanese pottery or calligraphy, becomes an act of presence a meditative dialogue between the mind and the material. In a world driven by automation and speed, this spirit reminds us that the deepest satisfaction still comes from doing one thing well, by hand, with heart. Final Thoughts This post marks a turning point for me and for this blog.I’ve spent decades building software, teams, and systems. I’ve seen tools come and go, frameworks rise and fade. But never before have we faced a transformation this deep one that challenges not just how we code, but why we code. Artisanal coding is my response.From this point forward, everything I write here every essay, every reflection will revolve around this principle: building software with intention, understanding, and care. This isn’t just about programming.It’s about reclaiming craftsmanship in a world addicted to shortcuts.It’s about creating something lasting in an era of instant everything.It’s about remembering that the hands still matter. “職人コーディング – Writing software with heart, precision, and purpose.” --- # Brainrot and the Slow Death of Code URL: https://www.ivanturkovic.com/2025/10/28/brainrot-and-the-slow-death-of-code/ Published: 2025-10-28 Topics: AI, Software Engineering It’s an uncomfortable thing to say out loud, but we’re witnessing a slow decay of human coding ability a collective brainrot disguised as progress. AI tools are rewriting how we build software. Every week, new developers boast about shipping apps in a weekend using AI assistants, generating entire APIs, or spinning up SaaS templates without understanding what’s going on beneath the surface. At first glance, this looks like evolution a leap forward for productivity. But beneath that veneer of efficiency, something essential is being lost. Something deeply human. The Vanishing Craft Coding has always been more than just typing commands into a terminal. It’s a way of thinking. It’s logic, structure, and creativity fused into a single process the art of turning chaos into clarity. But when that process is replaced by autocomplete and code generation, the thinking disappears. The hands still move, but the mind doesn’t wrestle with the problem anymore. The apprentice phase the long, painful, necessary stage of learning how to structure systems, debug, refactor, and reason gets skipped. And that’s where the rot begins. AI gives us perfect scaffolding but no understanding of the architecture. Developers start to “trust” the model more than themselves. Code review becomes an act of blind faith, and debugging turns into a guessing game of prompts. The craft is vanishing. We Are Losing Muscle Memory Just like a musician who stops practicing loses touch with their instrument, coders are losing their “muscle memory.” When you stop writing code line by line, stop thinking about data flow, stop worrying about algorithms and complexity your instincts dull. The small patterns that once made you fast, efficient, and insightful fade away. Soon, you can’t feel when something’s wrong with a function or a model. You can’t spot the small design flaw that will turn into technical debt six months later. You can’t intuit why the system slows down, or why memory leaks appear. AI-generated code doesn’t teach you these instincts it just hides the consequences long enough for them to explode. Inferior Code, Hidden in Abundance We’re producing more code than ever before but most of it is worse. AI makes quantity trivial. Anyone can spin up ten microservices, fifty endpoints, and thousands of lines of boilerplate in an hour. But that abundance hides a dangerous truth: we are filling the digital world with code that nobody understands. Future engineers will inherit layers of opaque, AI-generated software systems without authors, without craftsmanship, without intention. It’s digital noise masquerading as innovation. This isn’t progress. It’s entropy. The Myth of "Productivity" The industry loves to equate productivity with success. But in software, speed isn’t everything. Some of the best systems ever built took time, reflection, and human stubbornness. We’re now in a paradox where developers produce more but learn less. Where every shortcut taken today adds future friction. The so-called “productivity gains” are borrowed time a loan with heavy interest, paid in debugging, maintenance, and fragility. When code becomes disposable, knowledge follows. And when knowledge fades, innovation turns into imitation. The Future Is Not Hopeless If We Choose Discipline The solution isn’t to reject AI it’s to reestablish the boundaries between tool and craftsman. AI should be your assistant, not your brain. It should amplify your understanding, not replace it. The act of writing, reasoning, and debugging still matters. You still need to understand the stack, the algorithm, the data flow. If you don’t, the machine will own your craft and eventually, your value. Software built by people who no longer understand code will always be inferior to software built by those who do. The future of code depends on preserving that human layer of mastery the part that questions, improves, and cares. Closing Thought What’s happening isn’t the death of coding it’s the death of depth. We’re watching a generation of builders raised on autocomplete lose touch with the essence of creation. The danger isn’t that AI will replace programmers. The danger is that programmers will forget how to think like programmers. Brainrot isn’t about laziness it’s about surrender. And if we keep surrendering our mental muscles to the machine, we’ll end up with a future full of code that works but no one knows why. --- # The Art of Reusability and Why AI Still Doesn’t Understand It URL: https://www.ivanturkovic.com/2025/10/24/the-art-of-reusability-and-why-ai-still-doesnt-understand-it/ Published: 2025-10-24 Topics: AI, Ruby on Rails, Software Engineering After writing about the team that deleted 200,000 lines of AI-generated code without breaking their app, a few people asked me: “If AI is getting so good at writing code, why can’t it also reuse code properly?” That’s the heart of the problem. AI can produce code.It can suggest patterns.But it doesn’t understand why one abstraction should exist and why another should not. It has no concept of design intent, evolution over time, or maintainability.And that’s why AI-generated code often fails at the very thing great software engineering is built upon: reusability. Reusability Isn’t About Copying Code Let’s start with what reusability really means. It’s not about reusing text.It’s about reusing thought. When you make code reusable, you’re encoding an idea a shared rule or process in one place, so it can serve multiple contexts.That requires understanding how your domain behaves and where boundaries should exist. Here’s a small example in Ruby 3.4: # A naive AI-generated version class InvoiceService def create_invoice(customer, items) total = items.sum { |i| i[:price] * i[:quantity] } tax = total * 0.22 { customer: customer, total: total, tax: tax, grand_total: total + tax } end def preview_invoice(customer, items) total = items.sum { |i| i[:price] * i[:quantity] } tax = total * 0.22 { preview: true, total: total, tax: tax, grand_total: total + tax } end end It works. It looks fine.But the duplication here is silent debt. A small tax change or business rule adjustment would require edits in multiple places which the AI wouldn’t warn you about. Now, here’s how a thoughtful Rubyist might approach the same logic: class InvoiceCalculator TAX_RATE = 0.22 def initialize(items) @items = items end def subtotal = @items.sum { |i| i[:price] * i[:quantity] } def tax = subtotal * TAX_RATE def total = subtotal + tax end class InvoiceService def create_invoice(customer, items, preview: false) calc = InvoiceCalculator.new(items) { customer: customer, total: calc.subtotal, tax: calc.tax, grand_total: calc.total, preview: preview } end end Now the logic is reusable, testable, and flexible.If tax logic changes, it’s centralized.If preview behavior evolves, it stays isolated. This is design thinking not just text prediction. Why AI Struggles with This AI doesn’t understand context it understands correlation. When it generates code, it pulls from patterns it has seen before. It recognizes that “invoices” usually involve totals, taxes, and items.But it doesn’t understand the relationship between those things in your specific system. It doesn’t reason about cohesion (what belongs together) or coupling (what should stay apart). That’s why AI-generated abstractions often look reusable but aren’t truly so.They’re usually overgeneralized (“utility” modules that do too much) or underabstracted (duplicate logic with slightly different names). In other words:AI doesn’t design for reuse it duplicates for confidence. A Real Example: Reusability in Rails Let’s look at something familiar to Rubyists: ActiveRecord scopes. An AI might generate this: class Order < ApplicationRecord scope :completed, -> { where(status: 'completed') } scope :recent_completed, -> { where(status: 'completed').where('created_at > ?', 30.days.ago) } end Looks fine, right?But you’ve just duplicated the status: 'completed' filter. A thoughtful approach is: class Order < ApplicationRecord scope :completed, -> { where(status: 'completed') } scope :recent, -> { where('created_at > ?', 30.days.ago) } scope :recent_completed, -> { completed.recent } end It’s subtle but it’s how reusability works.You extract intent into composable units.You think about how the system wants to be extended later. That level of foresight doesn’t exist in AI-generated code. The Human Element: Judgment and Intent Reusability isn’t just an engineering principle it’s a leadership one. Every reusable component is a promise to your future self and your team.You’re saying: “This logic is safe to depend on.” AI can’t make that promise.It can’t evaluate trade-offs or organizational conventions.It doesn’t know when reuse creates value and when it adds friction. That’s why good engineers are editors, not just producers.We don’t chase volume; we curate clarity. My Takeaway AI is incredible at generating examples.But examples are not design. Reusability real, human-level reusability comes from understanding what stays constant when everything else changes.And that’s something no model can infer without human intent behind it. So yes AI can write Ruby.It can even generate elegant-looking methods.But it still can’t think in Ruby.It can’t feel the rhythm of the language, or the invisible architecture behind a clean abstraction. That’s still our job. And it’s the part that makes engineering worth doing. Written by Ivan Turkovic; technologist, Rubyist, and blockchain architect exploring how AI and human craftsmanship intersect in modern software engineering. --- # The AI Detox Movement: Why Engineers Are Taking Back Their Code URL: https://www.ivanturkovic.com/2025/10/23/the-ai-detox-movement-why-engineers-are-taking-back-their-code/ Published: 2025-10-23 Topics: AI, Career & Leadership, Software Engineering The New Reality of Coding in 2025 Over the last year, something remarkable happened in the world of software engineering. AI coding tools Cursor, GitHub Copilot, Cody, Devin became not just sidekicks, but full collaborators. Autocomplete turned into full functions, boilerplate became one-liners, and codebases that once took weeks to scaffold could now appear in minutes. It felt like magic. Developers were shipping faster than ever. Teams were hitting deadlines early. Startups were bragging about “AI-assisted velocity.” But behind that rush of productivity, something else began to emerge a quiet, growing discomfort. The Moment the Magic Fades After months of coding with AI, many developers hit the same wall.They could ship fast, but they couldn’t debug fast. When production went down, it became painfully clear: they didn’t truly understand the codebase they were maintaining. A backend engineer told me bluntly: “Cursor wrote the service architecture. I just glued things together. When it broke, I realized I had no idea how it even worked.” AI wasn’t writing bad code it was writing opaque code.Readable but not intuitive. Efficient but alien. This is how the term AI detox started spreading in engineering circles developers deliberately turning AI off to reconnect with the craft they’d begun to lose touch with. What Is an AI Detox? An AI detox is a deliberate break from code generation tools like Copilot, ChatGPT, or Cursor to rebuild your programming intuition, mental sharpness, and problem-solving confidence. It doesn’t mean rejecting AI altogether.It’s about recalibrating your relationship with it. Just as a fitness enthusiast might cycle off supplements to let their body reset, engineers are cycling off AI to let their brain do the heavy lifting again. Why AI Detox Matters The longer you outsource cognitive effort to AI, the more your engineering instincts fade.Here’s what AI-heavy coders have reported after several months of nonstop use: Reduced understanding of code structure and design choices. Slower debugging, especially in unfamiliar parts of the codebase. Weaker recall of language and framework features. Overreliance on generated snippets that “just work” without deeper understanding. Loss of flow, because coding became about prompting rather than creating. You might still be productive but you’re no longer learning.You’re maintaining an illusion of mastery. The Benefits of an AI Detox After even a short AI-free period, developers often notice a profound change in how they think and code: Deeper comprehension: You start to see the architecture again. Better debugging: You can trace logic without guesswork. Sharper recall: Syntax, libraries, and idioms return to muscle memory. Creative problem solving: You find better solutions instead of the first thing AI offers. Reconnection with craftsmanship: You take pride in code that reflects your thought process. As one engineer put it: “After a week without Cursor, I remembered how satisfying it is to actually solve something myself.” How to Plan Your AI Detox (Step-by-Step Guide) You don’t need to quit cold turkey forever.A structured plan helps you recoup your skills while keeping your work flowing. Here’s how to do it effectively: Step 1: Define Your Motivation Start by asking: What do I want to regain? Is it confidence? Speed? Understanding? Do I want to rebuild my debugging skills or architectural sense? Write it down. Clarity gives your detox purpose and prevents you from quitting halfway. Step 2: Choose Your Detox Duration Different goals require different lengths: Detox LevelDurationBest ForMini-detox3 daysA quick reset and self-checkWeekly detox1 full weekRebuilding confidence and recallExtended detox2–4 weeksDeep retraining of fundamentals If you’re working on a production project, start with a hybrid approach:AI-free mornings, AI-assisted afternoons. Step 3: Set Clear Rules Be explicit about what’s allowed and what’s not. Example rules: ✅ Allowed: Using AI for documentation lookups Reading AI explanations for existing code Asking conceptual questions (“How does event sourcing work?”) ❌ Not allowed: Code generation (functions, modules, tests, migrations) AI refactors or architecture design Using AI to debug instead of reasoning it out yourself The stricter the rule set, the greater the benefit. Step 4: Pick a Suitable Project Choose something that forces you to think but won’t jeopardize production deadlines. Good choices: Refactor an internal service manually. Build a small CLI or API from scratch. Rewrite a module in a different language (e.g., Ruby → Rust). Add integration tests by hand. Bad choices: Complex greenfield features with high delivery pressure. Anything that will make your manager panic if it takes longer. The goal is to practice thinking, not to grind deadlines. Step 5: Journal Your Learning Keep a daily log of what you discover: What took longer than expected? What concepts surprised you? What patterns do you now see more clearly? Which parts of the language felt rusty? At the end of the detox, you’ll have a personal reflection guide a snapshot of how your brain reconnected with the craft. Step 6: Gradually Reintroduce AI (With Boundaries) After your detox, it’s time to reintroduce AI intentionally. Here’s how to keep your skills sharp while benefiting from AI assistance: Use CaseAI UsageBoilerplate✅ Yes (setup, configs, tests)Core logic⚠️ Only for brainstorming or reviewingDebugging✅ For hints, but reason manually firstArchitecture✅ As a sounding board, not a decision-maker You’ll quickly find a balance where AI becomes an amplifier not a crutch. Example AI-Detox Schedule (4-Week Plan) Here’s a simple structure to follow: Week 1 – Awareness Turn off AI for 3 days. Focus on small, isolated tasks. Note moments where you instinctively reach for AI. Goal: Realize how often you rely on it. Week 2 – Manual Mastery Full AI-free week. Rebuild a module manually. Write comments before coding. Practice debugging from logs and stack traces. Goal: Relearn problem-solving depth. Week 3 – Independent Architecture Design and code a feature without any AI input. Document design decisions manually. Refactor and test it by hand. Goal: Restore confidence in end-to-end ownership. Week 4 – Rebalance Reintroduce AI, but only for non-critical parts. Review old AI-generated code and rewrite one section by hand. Evaluate your improvement. Goal: Reclaim control. Let AI assist, not lead. Practical Tips to Make It Work Disable AI in your editor: Don’t rely on willpower remove temptation. Pair program with another human: It recreates the reasoning process that AI shortcuts. Keep a “questions log”: Every time you’re tempted to ask AI something, write it down. Research it manually later. Revisit fundamentals: Review algorithms, frameworks, or patterns you haven’t touched in years. Read real code: Open-source repositories are the best detox material real logic, real humans. The Mindset Behind the Detox The purpose of an AI detox isn’t to prove you can code without AI.It’s to remember why you code in the first place. Good engineering is about understanding, design, trade-offs, and problem-solving.AI tools are brilliant at generating text but you are the one making decisions. The best developers I know use AI with intent. They use it to: Eliminate repetition. Accelerate boilerplate. Explore ideas. But they write, refactor, and debug the hard parts themselves because that’s where mastery lives. The Future Is Balanced AI isn’t going away. It’s evolving faster than any tool in tech history.But if you want to stay valuable as a developer, you need to own your code, not just generate it. The engineers who thrive over the next decade will be those who: Think independently. Understand systems deeply. Use AI strategically, not passively. Keep their fundamentals alive through intentional detox cycles. AI is a force multiplier not a replacement for your mind. So take a week. Turn it off.Write something from scratch.Struggle a little. Think a lot.Reignite the joy of building with your own hands. When you turn the AI back on, you’ll see it differently not as your replacement, but as your apprentice. --- # When 200,000 Lines of AI Code Disappeared and Nothing Broke URL: https://www.ivanturkovic.com/2025/10/21/when-200000-lines-of-ai-code-disappeared-and-nothing-broke/ Published: 2025-10-21 Topics: AI, Software Engineering A few weeks ago, someone I know a smart, capable engineering lead told me about their team’s strange success story. They deleted 200,000 lines of AI-generated code. And their app still worked. That alone tells you everything you need to know about the quiet cost of unchecked AI-assisted development. The project had originally been around 100,000 lines already a decent size for what it did. But over time, it ballooned to more than double that number. Most of the bloat came not from features or performance improvements, but from auto-generated boilerplate, duplicated logic, and abstractions no one really understood anymore. When they finally audited the system, they realized how much noise had crept in how much invisible entropy had been introduced under the banner of “productivity.” They cleaned it up. They deleted code. They refactored by hand. And the product kept running, smoother than before. The Illusion of Productivity This is the side of AI coding no one talks about. Yes AI can make you faster. But “faster” at what, exactly? If your processes, architecture, and reviews are already weak, AI will accelerate your chaos. It doesn’t understand your domain. It doesn’t see the trade-offs. It just predicts what “looks right.” And that’s exactly the problem: AI-generated code looks right.It compiles. It passes shallow tests. It feels complete. But under the surface, it’s often redundant, brittle, and opaque a kind of technical debt that doesn’t announce itself until you try to build on top of it. I’ve seen teams overwhelmed by maintenance of code they didn’t truly write.I’ve seen projects bloated with functions that appear useful but contribute nothing.I’ve even seen leaders puzzled when productivity metrics looked great while actual delivery velocity slowed to a crawl. The AI didn’t break the system.It just quietly magnified the team’s existing weaknesses. AI Is a Force Multiplier not a Substitute for Discipline This story reinforced something I’ve believed for a while: AI won’t fix your architecture.It won’t make your team more thoughtful.It won’t improve communication.And it definitely won’t tell you when the thing it just generated is complete nonsense. If your engineering culture is strong clean codebase, thoughtful design reviews, experienced developers who understand trade-offs then AI can be a genuine accelerant. It can help prototype ideas, fill in routine boilerplate, or refactor safely with guidance. But without that foundation, AI becomes an amplifier of dysfunction.It scales everything the good, the bad, and the ugly. The Temptation of the “Autonomous Engineer” I understand the temptation.The promise of AI development tools is seductive: faster output, lower costs, instant scaffolding. But I’ve learned that software isn’t about writing more code it’s about writing less code that does more work. The best engineers I’ve worked with are ruthless editors.They remove complexity.They delete unnecessary abstractions.They value clarity over cleverness, and design over automation. That discipline doesn’t go away just because a machine can now autocomplete functions. If anything, it becomes more important than ever. My Takeaway When that lead told me they’d deleted 200,000 lines of AI-generated code and everything still worked, I didn’t see it as a failure of the technology. I saw it as a reminder that tools don’t replace engineering principles. AI is a powerful assistant.But trust it blindly, and it will quietly erode your system from the inside out. The real productivity gain isn’t in the speed of generation it’s in the quality of judgment behind what stays and what gets deleted. Use AI. Experiment with it.But never forget: your codebase reflects your discipline, not your tools. And discipline is still something only humans can provide. Written by Ivan Turkovic; a technologist, Rubyist, and blockchain architect exploring how AI, code quality, and engineering culture shape the future of software. --- # Why AI Can’t (Yet) Write Maintainable Software URL: https://www.ivanturkovic.com/2025/10/17/why-ai-cant-yet-write-maintainable-software/ Published: 2025-10-17 Topics: AI, Software Engineering In the past few years, large language models (LLMs) have burst onto the software development scene like a meteor bright, exciting, and full of promise. They can write entire applications in seconds, generate boilerplate code with ease, and explain complex algorithms in plain English. It’s hard not to be impressed. But after spending serious time testing various AI platforms as coding assistants, I’ve reached a clear conclusion: AI is not yet suitable for generating long-term, maintainable, production-grade software. It’s fantastic for prototyping, disposable tools, and accelerating development, but when it comes to real-world, evolving, multi-developer systems it falls short. And the root cause is simple but fundamental: non-determinism. The Non-Determinism Problem At the heart of every LLM lies a probabilistic process. When you ask an AI to write or modify code, it doesn’t “recall” what it said before it predicts the next most likely word or token based on the context it sees. Even when you give it the exact same prompt twice, you often get subtly (or wildly) different answers. In casual conversation, this doesn’t matter much. But in software engineering, determinism is sacred. A build must produce the same binary every time. Tests must behave consistently. A function’s output must depend solely on its input. LLMs break this rule by design. When you ask AI to “add a new field to this API,” it might add the field but it might also rename unrelated variables, adjust indentation styles, reorder imports, or subtly alter unrelated logic. These incidental changes make it almost impossible to track what actually changed and why. In version control, that’s noise. In production code, that’s risk. The Illusion of Velocity Using AI for coding can feel like flying until you realize you’ve lost track of where you’re going. AI-generated code feels fast. You type a prompt, and it spits out a function that looks plausible. But as any experienced engineer knows, code that looks correct is not the same as code that is correct. Worse still, AI often gets 90% right just enough to lull you into trusting it, but that last 10% (the edge cases, performance issues, or security vulnerabilities) can be costly. In long-lived systems, those flaws become ticking time bombs. So yes, AI saves time but only if you’re ready to spend that saved time reviewing, refactoring, and making it consistent with your project standards. Otherwise, you’re borrowing technical debt against future maintenance. “Vibe Coding” vs. Real Engineering There’s a growing trend I like to call “vibe coding” relying on AI to produce code that “feels” right without understanding it deeply. It’s seductive, especially for less experienced developers or under time pressure. But the truth is: software longevity is built on understanding, not vibes. A healthy codebase is not just functional it’s coherent, documented, and maintainable. Every class, function, and comment exists for a reason that another human can later understand. AI-generated code often lacks that intentionality. It can mimic style, but it doesn’t comprehend architecture, team conventions, or long-term evolution. AI doesn’t “see” the whole system it only sees your current prompt. Where AI Does Shine Despite these limitations, I’m not anti-AI. In fact, I use it daily strategically. AI is brilliant at: Prototyping ideas getting something working fast, even if it’s messy. Generating boilerplate writing repetitive CRUD or setup code. Explaining code translating complex logic into human-readable summaries. Brainstorming solutions helping you think through alternative approaches. Writing tests drafting coverage you can refine manually. In other words, AI accelerates cognition, not automation. It’s a thinking partner, not a replacement for engineering discipline. What It Means for the Future As LLMs improve, we’ll likely see more deterministic, context-aware systems perhaps ones that can “anchor” to a codebase and learn its structure persistently. But until then, the responsibility for coherence, maintainability, and correctness still lies with us the humans. AI might be the apprentice, but we’re still the architects. My takeaway after months of experimentation is simple: Use AI to accelerate development, not to abdicate responsibility. Treat its output like an intern’s draft: useful, fast, and full of potential but never production-ready without review, cleanup, and integration into your project’s ecosystem. The Bottom Line AI coding tools are a revolution but like every revolution, they require balance and maturity to use effectively. They’re not replacing software engineers; they’re augmenting them. So go ahead, let the AI write your prototypes, mock APIs, or test scaffolds. But when it comes to the production systems that real users depend on make sure there’s a human behind the keyboard who understands every line. Because in the end, the difference between disposable and durable code isn’t who (or what) wrote it it’s who owns it. --- # Returning to the Rails World: What’s New and Exciting in Rails 8 and Ruby 3.3+ URL: https://www.ivanturkovic.com/2025/10/16/returning-to-the-rails-world-whats-new-and-exciting-in-rails-8-and-ruby-3-3/ Published: 2025-10-16 Topics: Career & Leadership, Ruby on Rails, Software Engineering, Startups & Business It’s 2025, and coming back to Ruby on Rails feels like stepping into a familiar city only to find new skyscrapers, electric trams, and an upgraded skyline.The framework that once defined web development simplicity has reinvented itself once again. If you’ve been away for a couple of years, you might remember Rails 6 or early Rails 7 as elegant but slightly “classic.”Fast-forward to today: Rails 8 and Ruby 3.4 together form one of the most modern, high-performance, and full-stack ecosystems in web development. Let’s explore what changed from Ruby’s evolution to Rails’ latest superpowers. The Ruby Renaissance: From 3.2 to 3.4 Over the last two years, Ruby has evolved faster than ever.Performance, concurrency, and developer tooling have all received major love while the language remains as expressive and joyful as ever. Ruby 3.2 (2023): The Foundation of Modern Ruby YJIT officially production-ready: Introduced a new JIT compiler written in Rust, delivering 20–40% faster execution on Rails apps. Prism Parser (preview): The groundwork for a brand-new parser that improves IDEs, linters, and static analysis. Regexp improvements: More efficient and less memory-hungry pattern matching. Data class proposal: Early syntax experiments to make small, immutable data structures easier to define. Ruby 3.3 (2024): Performance, Async IO, and Stability YJIT 3.3 update: Added inlining and better method dispatch caching big wins for hot code paths. Fiber Scheduler 2.0: Improved async I/O great for background processing and concurrent network calls. Prism Parser shipped: Officially integrated, paving the way for better tooling and static analysis. Better memory compaction: Long-running apps now leak less and GC pauses are shorter. Ruby 3.4 (2025): The Next Leap Prism as the default parser making editors and LSPs much more accurate. Official WebAssembly build: You can now compile and run Ruby in browsers or serverless environments. Async and Fibers 3.0: Now tightly integrated into standard libraries like Net::HTTP and OpenURI. YJIT 3.4: Huge startup time and memory improvements for large Rails codebases. Smarter garbage collector: Dynamic tuning for better throughput under load. Example: Native Async Fetching in Ruby 3.4 require "async" require "net/http" Async do ["https://rubyonrails.org", "https://ruby-lang.org"].each do |url| Async do res = Net::HTTP.get(URI(url)) puts "#{url} → #{res.bytesize} bytes" end end end That’s fully concurrent, purely in Ruby no threads, no extra gems.Ruby has quietly become fast, efficient, and concurrent while keeping its famously clean syntax. The Rails Revolution: From 7 to 8 While Ruby evolved under the hood, Rails reinvented the developer experience.Rails 7 introduced the “no-JavaScript-framework” movement with Hotwire.Rails 8 now expands that vision making real-time, async, and scalable apps easier than ever. Rails 7 (2022–2024): The Hotwire Era Rails 7 changed the front-end game: Hotwire (Turbo + Stimulus): Replaced complex SPAs with instant-loading server-rendered apps. Import maps: Let you skip Webpack entirely. Encrypted attributes: encrypts :email became a one-line reality. ActionText and ActionMailbox: Brought full-stack communication features into Rails core. Zeitwerk loader improvements: Faster boot and reloading in dev mode. Example: Rails 7 Hotwire Simplicity # app/controllers/messages_controller.rb def create @message = Message.create!(message_params) turbo_stream.append "messages", partial: "messages/message", locals: { message: @message } end That’s a live-updating chat stream with no React, no WebSocket boilerplate. Rails 8 (2025): Real-Time, Async, and Database-Native Rails 8 takes everything Rails 7 started and levels it up for the next decade. Turbo 8 and Turbo Streams 2.0 Hotwire gets more powerful: Streaming updates from background jobs Improved Turbo Frames for nested components Async rendering for faster page loads class CommentsController < ApplicationController def create @comment = Comment.create!(comment_params) turbo_stream.prepend "comments", partial: "comments/comment", locals: { comment: @comment } end end Now you can push that stream from Active Job or Solid Queue, enabling real-time updates across users. Solid Queue and Solid Cache Rails 8 introduces two built-in frameworks that change production infrastructure forever: Solid Queue: Database-backed job queue think Sidekiq performance without Redis. Solid Cache: Native caching framework that integrates with Active Record and scales horizontally. # Example: background email job using Solid Queue class UserMailerJob < ApplicationJob queue_as :mailers def perform(user_id) UserMailer.welcome_email(User.find(user_id)).deliver_now end end No Redis, no extra service everything just works out of the box. Async Queries and Connection Pooling Rails 8 adds native async database queries and automatic connection throttling for multi-threaded environments.This pairs perfectly with Ruby’s improved Fiber Scheduler. users = ActiveRecord::Base.async_query do User.where(active: true).to_a end Smarter Defaults, Stronger Security Active Record Encryption expanded with deterministic modes Improved CSP and SameSite protections Rails generators now use more secure defaults for APIs and credentials Developer Experience: Rails Feels Modern Again The latest versions of Rails and Ruby have also focused heavily on DX (developer experience). bin/rails console --sandbox rolls back all changes automatically. New error pages with interactive debugging. ESBuild 3 & Bun support for lightning-fast JS builds. Improved test parallelization with async jobs and Capybara integration. ViewComponent and Hotwire integration right from generators. Rails in 2025 feels sleek, intelligent, and incredibly cohesive. The Future of Rails and Ruby Together With Ruby 3.4’s concurrency and Rails 8’s async, streaming, and caching power, Rails has evolved into a true full-stack powerhouse again capable of competing with modern Node, Elixir, or Go frameworks while staying true to its elegant roots. It’s not nostalgia it’s progress built on the foundation of simplicity. If you left the Rails world thinking it was old-fashioned, this is your invitation back.You’ll find your favorite framework faster, safer, and more capable than ever before. Posted by Ivan TurkovicRubyist, software engineer, and believer in beautiful code. --- # What You Should Learn to Master but Never Ship URL: https://www.ivanturkovic.com/2025/10/15/what-you-should-learn-to-master-but-never-ship/ Published: 2025-10-15 Topics: AI, Career & Leadership, Software Engineering, Startups & Business There’s a quiet rite of passage every engineer goes through. You build something that already exists. You write your own search algorithm. You design your own auth system. You roll your own logging framework because the existing one feels too heavy. And for a while, it’s exhilarating. You’re learning, stretching, discovering how the pieces actually work. But there’s a difference between learning and shipping. The Temptation to Reinvent Every generation of engineers rediscovers the same truth: we love building things from scratch. We tell ourselves our use case is different, our system is simpler, our constraints are unique. But the moment your code touches production when it has to handle real users, scale, security, and compliance you realize how deep the rabbit hole goes. Here’s a short list of what you probably shouldn’t reinvent if your goal is to ship something that lasts: Search algorithms Encryption Authentication Credit card handling Billing Caching systems Logging frameworks CSV, HTML, URL, JSON, XML parsing Floating point math Timezones Localization and internationalization Postal address handling Each one looks simple on the surface. Each one hides decades of hard-won complexity underneath. Learn It, Don’t Ship It You should absolutely build these things once. Do it for the same reason musicians practice scales or pilots train in simulators. You’ll understand the invisible edges where systems fail, what tradeoffs libraries make, how standards evolve. Build your own encryption to see why key rotation matters.Write your own caching layer to feel cache invalidation pain firsthand.Parse CSVs manually to understand why “CSV” isn’t a real standard. You’ll emerge humbled, smarter, and far less likely to call something “trivial” again. But then don’t ship it. The Cost of Cleverness Production is where clever ideas go to die. The real cost of rolling your own isn’t just the initial build. It’s the invisible tax that compounds over time: maintenance, updates, edge cases, security audits, integration testing. That custom auth system? It’ll need to handle password resets, MFA, SSO, OAuth, token expiration, brute-force protection, and GDPR deletion requests. Your homegrown billing service? Get ready for tax handling, currency conversion, refund flows, audit trails, and legal exposure. Most of us underestimate this cost by an order of magnitude. And that gap between what you think you built and what reality demands is where projects go to die. The Wisdom of Boring Software Mature engineering isn’t about novelty it’s about leverage. When you use battle-tested libraries, you’re not being lazy. You’re standing on top of millions of hours of debugging, testing, and iteration that others have already paid for. The best engineers I know are boring. They use Postgres, Redis, S3. They trust Stripe for billing, Auth0 for authentication, Cloudflare for caching. They’d rather spend their creative energy on business logic and user experience the parts that actually differentiate a product. Boring software wins because it doesn’t collapse under its own cleverness. Why This Matters Even More in the AI Era Today, a new kind of abstraction has arrived: AI.We don’t just import libraries anymore we import intelligence. When you integrate AI into your workflow, you’re effectively outsourcing judgment, reasoning, and data handling to a black box that feels magical but is still software under the hood. If you’ve never built or understood the underlying systems search, parsing, data handling, caching, numerical precision you’ll have no intuition for what the AI is actually doing. You’ll treat it as oracle instead of a tool. Knowing how these fundamentals work grounds you. It helps you spot when the model hallucinates, when latency hides in API chains, when an embedding lookup behaves like a fuzzy search instead of real understanding. The engineers who will thrive in the AI era aren’t the ones who blindly prompt. They’re the ones who know what’s happening behind the prompt. Because AI systems don’t erase complexity they just bury it deeper. And if you don’t know what lives underneath, you can’t debug, govern, or trust it. When It’s Worth Reinventing There are exceptions. Sometimes the act of rebuilding is the product itself. Search at Google. Encryption at Signal. Auth at Okta. If your business is the infrastructure, then yes go deep. Reinvent with intention. But if it’s not, your job is to assemble reliable systems, not to recreate them. Learn enough to understand the tradeoffs, but don’t mistake knowledge for necessity. The Real Lesson Here’s the paradox: you can’t truly respect how hard these problems are until you’ve built them yourself. So do it once. In a sandbox, on weekends, or as a thought exercise. Feel the pain, appreciate the elegance of the libraries you once dismissed, and move on. That humility will make you a better engineer and a more trusted builder in the AI age than any clever homegrown library ever could. Final thought:Master everything. Ship selectively. That’s the difference between engineering as craft and engineering as production.And it’s the difference between using AI and actually understanding it. --- # AI Vibe Coding vs. Outsourcing vs. Local Developers. What Really Works Best URL: https://www.ivanturkovic.com/2025/10/11/ai-vibe-coding-vs-outsourcing-vs-local-developers-what-really-works-best/ Published: 2025-10-11 Topics: AI, Software Engineering The way we build software is changing fast.You can now code alongside AI in real time. You can hire an offshore team across time zones. Or you can build with local developers right next to you the old-school way that suddenly feels new again. Each model works, but they work differently. And when it comes to product quality, iteration speed, and long-term success only one consistently delivers. Let’s unpack all three, step by step. AI Vibe Coding is Building at the Speed of Thought AI vibe coding is when you work directly with AI tools like ChatGPT, Claude, or GitHub Copilot as your pair developer. It’s not about asking for snippets it’s about co-developing live.You describe your intent, get code, refine it instantly, and iterate in a tight feedback loop. Process (Pseudocode-Style) while (building_feature): describe_intent_to_ai("Create an onboarding flow with email + OAuth") ai.generates_scaffold() you.review_and_edit_live() ai.adjusts_structure_and_tests() run_tests() deploy_if_ready() Pros ✅ Extremely fast iteration✅ Context-aware (if you prompt consistently)✅ Great for prototyping and boilerplate✅ Ideal for solo founders or small teams Cons ⚠️ Requires strong technical judgment⚠️ AI lacks product intuition and domain empathy⚠️ Risk of hidden bugs or overconfidence in generated code⚠️ Limited long-term maintainability without review AI vibe coding accelerates early-stage building but it still needs human oversight, structured review, and context. It’s great for speed, not yet for strategy. Outsourcing Development, Slow Communication, Slow Momentum Outsourcing means hiring remote developers (often overseas) to build parts or all of your product. The promise: cost savings and flexibility.The reality: delays, misunderstandings, and low-context execution. Process (Pseudocode-Style) while (project_in_progress): product_owner.create_ticket(): - detailed specs - screenshots, examples, acceptance criteria offshore_team(next_day): - reads ticket - implements as interpreted - opens PR for review product_owner(next_morning): - reviews PR - finds edge cases or misalignment - leaves comments Pros ✅ Lower hourly cost✅ Access to global talent✅ Good for simple, well-scoped tasks Cons ⚠️ Delayed feedback loops (timezone lag)⚠️ Communication loss and misinterpretation⚠️ Reduced ownership or creative input⚠️ Code that “works” but doesn’t “fit”⚠️ High hidden cost in management and rework Outsourcing works if your specs are crystal clear and your needs don’t change.But in reality product needs always change. Local Developers, Real-Time Collaboration and True Ownership Now let’s talk about the model that consistently wins: local developers. Whether sitting next to you or just in the same time zone, local developers bring both technical skill and product empathy. They understand your users, your goals, and your market context intuitively. This creates a feedback loop that’s impossible to replicate with outsourcing or AI. Process (Pseudocode-Style) while (building_product): morning_sync(): - quick discussion on goals and blockers - align on what matters today devs.start_coding(): - spontaneous chat or screen share - brainstorm architecture in real time - fix and test instantly afternoon_review(): - peer review and refactor collaboratively - same-day deploy Pros ✅ Real-time communication✅ Shared product understanding✅ Collaborative brainstorming✅ High accountability and quality✅ Culture and creativity alignment Cons ⚠️ Higher cost per developer⚠️ Limited local hiring pool⚠️ Needs strong leadership and culture But what you gain far outweighs the cost.When your developers vibe with your product, decisions are faster, reviews are deeper, and every line of code carries intent. The Hidden Layer, How Context Shapes Code Here’s the truth:Every developer human or AI writes code based on context. When context is broken (as in outsourcing), code quality drops.When context is partial (as with AI), you get speed but need oversight.When context is shared (as with local devs), you get clarity, accountability, and alignment. The Context Pyramid LayerOutsourcingAI Vibe CodingLocal DeveloperProduct intuitionLowMediumHighSpeedLowVery HighHighCollaboration depthLowMediumVery HighCommunication lagHighNoneMinimalCode qualityVariableGood with reviewConsistently strongOwnershipLowNoneHighBest forFixed-scope tasksRapid prototypingLong-term, evolving products The Hybrid Reality The future is likely hybrid: Use AI for ideation, scaffolding, and acceleration. Avoid outsourcing for evolving or strategic projects. Anchor everything around a local team that owns the product, understands the users, and ensures quality. That’s the winning setup AI for speed, local developers for soul. 💬 Final Thoughts Building great software isn’t just about writing code.It’s about alignment shared context, communication, and ownership. AI vibe coding gives you momentum. Outsourcing gives you manpower. Local developers give you meaning and mastery. If you want code that not only runs but lasts go local, stay collaborative, and use AI as your accelerator, not your replacement. 👨‍💻 Need Help Structuring Your Team or Workflow? If you’re building an MVP, scaling a startup, or managing tech in fintech or SaaS that needs both speed and reliability, I can help. With nearly two decades of experience building, scaling, and advising startups and complex systems, I offer consulting on how to structure teams, integrate AI effectively, and build codebases that actually scale and stand the test of time. Let’s make your team and your code vibe. --- # ?? → BI → ML → AI → ?? URL: https://www.ivanturkovic.com/2025/10/08/%e2%86%92-bi-%e2%86%92-ml-%e2%86%92-ai-%e2%86%92/ Published: 2025-10-08 Topics: AI, Career & Leadership, Startups & Business AI's past and the future Where acronyms in business come from, what they sold, who won, and what might come after “AI” Acronyms are the currency of business storytelling. They compress complex technology into a neat package a salesperson can pitch in a single slide: CRM, ERP, BI, ML, AI. Each one marked a shift in what companies sold to their customers and how value was captured. I want to walk through that history briefly, honestly, with business examples and what “winning” looked like in each era and then make a practical, evidence-based prediction for what comes after AI. I’ll finish with concrete signs companies and entrepreneurs should watch if they want to be on the winning side next. The pre-acronym age: data collectors and automation (before CRM/ERP took over) Before the catchy three-letter packages, businesses bought automation and niche systems: financial ledgers, bespoke reporting scripts, and the earliest mainframe systems. The selling point was efficiency: replace paper, reduce human error, scale payroll or accounting. Winners: large system integrators and early software firms that could deliver reliability and scale. Value to the customer was operational: fewer mistakes, faster month-end closes, predictable processes. This era set the expectation that software replaces tedious human work an expectation every later acronym exploited and monetized. CRM / ERP the era of process standardization and cross-company suites Acronyms like ERP and CRM told customers what problem a vendor solved: enterprise resource planning for the core business, customer relationship management for sales and marketing. The message was simple: centralize and standardize. Business sales example: SAP and Oracle sold ERP as a bet on process control; Siebel (then Oracle) sold CRM as the way to professionalize sales organizations. Projects were expensive, multi-year, and became investments in repeatability and governance. The commercial model was license + services. Success looked like longer, stickier contracts and high services revenue. Winners: vendors who could sell a vision of stability and then deliver implementation expertise. BI (Business Intelligence) data becomes a product BI formalized the idea that data itself is valuable: dashboards, reports, and the ability to make decisions from consolidated datasets. The term was popularized in the late 1980s and 1990s as companies realized that aggregated data and fact-based dashboards could change executive decision making. BI vendors promised that data could be turned into actionable insight. Business sales example: BusinessObjects, Cognos, MicroStrategy sold a reliable narrative centralize data, produce dashboards, enable managers to make informed choices. Customers were large enterprises whose decisions had big dollar consequences: pricing, inventory, and marketing allocation. Success metric: adoption by management, ROI from better decisions, and a move to subscription models as vendors evolved. BI also laid the foundation for data warehouses and ETL pipelines the plumbing later eras would rely on. ML (Machine Learning) predictions replace static dashboards Machine learning shifted the promise from describing the past to predicting the future. ML isn’t a single product but a set of techniques that let systems learn patterns recommendations, fraud detection, demand forecasting. Its commercialization accelerated as larger datasets and compute made models useful in production. (Timeline of ML milestones is long from perceptrons to ImageNet and modern deep learning.) Business sales example: Netflix used ML for recommendations (watch time → retention); Amazon used ML for recommendations and dynamic pricing; banks used ML for fraud detection. The product pitch became “we will increase revenue (or reduce losses) by X% using model-driven predictions.” Success metric: measurable impact on key business metrics (conversion, churn, fraud rate) and repeatable MLops pipelines. Winning companies built both models and the integration into products and workflows the second part mattered as much as the model. AI (Artificial Intelligence) foundation models, agents, and ubiquity “AI” is a broader, more emotionally charged badge than ML. It promises not just predictions, but agency: systems that write, design, plan, and interact. The recent leap in capability comes from large foundation models and multimodal systems, and the market’s attention has become concentrated on a smaller set of platform players. OpenAI is the obvious poster child widely integrated and publicly visible and it’s now part of a small club of companies shaping how enterprises adopt AI. Others Anthropic, Google/DeepMind, Microsoft (as a partner and investor), Nvidia (as the infra champion) are also core to who wins in the AI era. Recent reporting and market movement underscore how concentrated and influential these players are. Business sales example: AI is sold as both a strategic platform and as task automation. Microsoft + OpenAI integrations sell enterprise productivity gains; Anthropic partners with platforms and enterprise vendors to bring chat/agent capabilities into products; Nvidia sells the hardware that makes large models economically viable. Sales morph into partnerships (platform + integration) and usage-based monetization (API calls, seats for AI assistants, compute consumption). Success metric: ecosystem adoption and sticky integrations. The winners aren’t just model makers they are the platforms that make models reliably usable within enterprise apps, the cloud vendors that provide infra, and the companies that embed AI into workflows to measurably lower costs or multiply revenue. What’s next? Predicting the post-AI acronym Acronyms rise from what businesses need to sell next. Right now, AI sells capability; tomorrow, the market will demand something different: not raw capability but safe, contextual, composable, and human-centric value. Based on where the money, engineering effort, and regulatory focus are going, here are a few candidate acronyms and my pick. Candidate futures (short list) CAI: Contextual AIFocus: models that understand user context (company data, regulations, customer history) and deliver context-aware outputs with provenance. Selling point: trust and relevance. Businesses pay for AI that "knows the company" and can operate under constraints. A^2I / AI²: Augmented & Autonomous IntelligenceFocus: agents that both augment humans and act autonomously on behalf of businesses (book meetings, negotiate, execute trades). Selling point: time reclaimed and tasks delegated with measurable outcomes. DAI: Distributed AIFocus: moving models to the edge, on-device privacy, and federated learning. Selling point: privacy, latency, and regulatory compliance. Monetization: device + orchestration + certification. HXI: Human-Centered Experience Intelligence (or HCI reimagined)Focus: design + AI that measurably improves human outcomes (productivity, wellbeing). Selling point: human adoption and long-term retention; less hype, more stickiness. XAI: Explainable AI (commercialized)Focus: regulations and auditability breed a market for explainable models as first-class products. Selling point: compliance, audit trails, and legally defensible automation. My prediction (the one I’d bet money on) CAI: Contextual AI.Why? The immediate commercial friction after capability is trust and integration. Companies will not pay forever for raw creativity if outputs can’t be traced to corporate data, policies, and goals. The era of foundation models created broad capabilities; the next era will productize those capabilities into contextualized, policy-aware services that integrate directly into enterprise systems (CRMs, ERPs, legal, finance) and produce auditable actions. In short: AI + enterprise context = the next product category. Concrete signs for CAI already exist: enterprises demanding model fine-tuning on private corpora, partnerships between model-makers and enterprise software vendors, and regulatory attention pushing for explainability and provenance. Those are the ingredients for a context-first commercial product. (If you prefer the agent narrative, A^2I where agents actually do things reliably and accountably is a close second. But agents without context are liability; agents with context are product.) What winning looks like in CAI If CAI becomes the next category, how do businesses win? Data integration champions vendors that make it trivial to connect enterprise data (ERP, CRM, contracts) to models with privacy and governance baked in. The sales pitch: “We connect, govern, and make AI outputs auditable.” Actionable interfaces not just a chat box, but agents that produce auditable actions inside workflows (e.g., “Create invoice,” “Propose contract clause,” “Adjust inventory reorder”). The pitch: “We reduce X hours/week for role Y.” Regulatory and risk products explainability, model cards, audit logs, and compliance workflows become table stakes. Vendors packaging those for regulated industries will command higher multiples. Infra + economics hardware and cloud vendors that optimize cost/performance for fine-tuned, context-rich models (Nvidia-like infra winners) will capture a slice. Recent market moves show infrastructure captures enormous value; watch the hardware and cloud players. Practical advice for sellers and builders today If you sell to enterprises: stop pitching "we use AI." Start pitching what measurable outcome you deliver and how you keep it governed. Show integration architecture diagrams: where the data lives, what’s fine-tuned, and where the audit logs are. If you build products: invest in connectors, provenance, and reversible actions. A product that lets customers roll back an AI decision will win trust and enterprise POs. If you’re an investor or operator: look for companies that own context (industry datasets, domain rules, vertical workflows). Horizontal foundation models will be commoditized; contextual wrappers will be the economic moat. If you’re an infra player: optimize for cost + compliance. The market will pay a premium for infra that matches enterprise security and cost constraints. Example scenarios; how each era turned into commercial value BI era: a retail chain buys a BI suite to consolidate POS data across stores. Result: optimized promotions, fewer stockouts, 3% margin improvement. The seller (BI vendor) expanded into recurring maintenance and cloud hosting. ML era: an e-commerce platform adds recommendation models. Result: personalized homescreens boost AOV by 7%. The ML vendor sells models + integration and gets paid per API call and for model retraining. AI era: an agency uses generative models to prototype marketing copy at scale. Result: faster iteration and lower creative costs; large platforms (OpenAI, Anthropic, Google) sell the models, cloud vendors sell the compute. OpenAI’s integrations made it a visible "winner" for developers and enterprises adopting chat/assistant features. CAI era (predicted): the same retail chain buys a contextual assistant that reads contracts, vendor SLAs, and inventory rules, then suggests optimal promotions aligned with margin and regulatory rules. Result: promotions that respect contracts, better margins, and an auditable decision trail. Pricing: subscription + outcome share. Acronyms are marketing. Value is behavioral change. Acronyms succeed when they promise a specific, repeatable business result and when vendors can deliver measurable change in behavior. BI helped managers act on facts. ML helped products predict user intent. AI made interaction and creativity broadly available. The next profitable acronym my money is on CAI (Contextual AI) will sell trustworthy, context-aware automation that actually becomes part of the way companies operate. If you’re building, selling, or investing: focus less on the label and more on the edges where value is realized integration, governance, measurable business outcomes. That’s where the next winners will be, and where your clients will write the checks. --- # The Vibe Code Tax URL: https://www.ivanturkovic.com/2025/09/29/the-vibe-code-tax/ Published: 2025-09-29 Topics: AI, Career & Leadership, Software Engineering, Startups & Business Momentum is the oxygen of startups. Lose it, and you suffocate. Getting it back is harder than creating it in the first place. Here’s the paradox founders hit early: Move too slowly searching for the “perfect” technical setup, and you’re dead before you start. Move too fast with vibe-coded foundations, and you’re dead later in a louder, more painful way. Both paths kill. They just work on different timelines. Death by Hesitation Friendster is a perfect example of death by hesitation. They had the idea years before Facebook. They had users. They had momentum. But their tech couldn’t scale, and instead of fixing it fast, they stalled. Users defected. Momentum bled out. By the time they moved, Facebook and MySpace had already eaten their lunch. That’s hesitation tax: waiting, tinkering, second-guessing while the world moves on. Death by Vibe Coding On the flip side, you get the vibe-coded death spiral. Take Theranos. It wasn’t just fraud, it was vibe coding at scale. Demos that weren’t real. A prototype paraded as a product. By the time the truth surfaced, they’d burned through billions and a decade of time. Or look at Quibi. They raced to market with duct-taped assumptions the whole business was a vibe-coded bet that people wanted “TV, but shorter.” $1.75 billion later, they discovered the foundation was wrong. That’s the danger of mistaking motion for progress. The Right Way to Use Vibe Coding Airbnb is the counterexample. Their first site was duct tape. Payments were hacked together. Listings were scraped. It was vibe code but they treated it as a proof of concept, not a finished product. The moment they proved demand (“people really will rent air mattresses from strangers”), they rebuilt. They didn’t cling to the prototype. They moved fast, validated, then leveled up. That’s the correct use: vibe code as validation, not as production. The Hidden Tax The vibe code tax is brutal because it’s invisible at first. It’s not just money. Lost time → The 6–12 months you’ll spend duct-taping something that later has to be rebuilt from scratch. Lost customers → Early adopters churn when they realize your product is held together with gum and string. Most won’t return. Lost momentum → Investors don’t like hearing “we’re rebuilding.” Momentum is a story you only get to tell once. And you don’t get to dodge this tax. You either pay it early (by finding a technical co-founder or paying real engineers), or you pay it later (through rebuilds, lost customers, and wasted months). How to Stay Alive Be honest. Call your vibe-coded MVP a prototype. Never pitch it as “production-ready.” Set a timer. Airbnb didn’t stay in duct tape land for years. They validated and moved on. You should too. Budget for the rebuild. If you don’t have a co-founder, assume you’ll need to pay engineers once the prototype proves itself. Go small but real. One feature built right is more valuable than ten features that crumble. Final Word The startup graveyard is full of companies that either waited too long or shipped too fast without a foundation. Friendster hesitated. Theranos faked it. Quibi mistook hype for traction. Airbnb survived because they paid the vibe code tax on their terms. They used duct tape to test, then rebuilt before the cracks became fatal. That’s the playbook. Because no matter what the vibe code tax always comes due. --- # Is AI Slowing Everyone Down? URL: https://www.ivanturkovic.com/2025/09/25/is-ai-slowing-everyone-down/ Published: 2025-09-25 Topics: AI, Career & Leadership Over the past year, we’ve all witnessed an AI gold rush. Companies of every size are racing to “adopt AI” before their competitors do, layering chatbots, content tools, and automation into their workflows. But here’s the uncomfortable question: is all of this actually making us more productive, or is AI quietly slowing us down? A new term from Harvard Business Review “workslop” captures what many of us are starting to see. It refers to the flood of low-quality, AI-generated work products: memos, reports, slide decks, emails, even code snippets. The kind of content that looks polished at first glance, but ultimately adds little value. Instead of clarity, we’re drowning in noise. The Illusion of Productivity AI outputs are fast, but speed doesn’t always equal progress. Generative AI makes it effortless to produce content, but that ease has created a different problem: oversupply. We’re seeing more documents, more proposals, more meeting summaries but much of it lacks originality or critical thought. When employees start using AI as a crutch instead of a tool, the result is extra layers of text that someone else has to review, fix, or ignore. What feels like efficiency often leads to more time spent filtering through workslop. The productivity gains AI promises on paper are, in practice, canceled out by the overhead of sorting the useful from the useless. Numbers Don’t Lie The MIT Media Lab recently published a sobering study on AI adoption. After surveying 350 employees, analyzing 300 public AI deployments, and interviewing 150 executives, the conclusion was blunt: Fewer than 1 in 10 AI pilot projects generated meaningful revenue. 95% of organizations reported zero return on their AI investments. The financial markets noticed. AI stocks dipped after the report landed, signaling that investors are beginning to question whether this hype cycle can sustain itself without real business impact. Why This Happens The root cause isn’t AI itself it’s how organizations are deploying it. Instead of rethinking workflows and aligning AI with core business goals, many companies are plugging AI in like a patch. “We need to use AI somewhere, anywhere.” The result is shallow implementations that create surface-level outputs without driving real outcomes. It’s the same mistake businesses made during earlier tech booms. Tools get adopted because of fear of missing out, not because of a well-defined strategy. And when adoption is guided by FOMO, the outcome is predictable: lots of activity, little progress. Where AI Can Deliver Despite the noise, I don’t think AI is doomed to be a corporate distraction. The key is focus. AI shines when it’s applied to specific, high-leverage problems: Automating repetitive, low-value tasks (think: data entry, scheduling, or document classification). Enhancing decision-making with real-time insights from complex data. Accelerating specialized workflows in domains like coding, design, or customer support if humans remain in the loop. The companies that will win with AI aren’t the ones pumping out endless AI-generated documents. They’re the ones rethinking their processes from the ground up and asking: Where can AI free humans to do what they do best? The Human Factor We have to remember: AI isn’t a replacement for judgment, creativity, or strategy. It’s a tool one that can amplify our abilities if used thoughtfully. But when used carelessly, it becomes a distraction that actually slows us down. The real productivity gains won’t come from delegating everything to AI. They’ll come from combining human strengths with AI’s capacity, cutting through the noise, and resisting the temptation to let machines do our thinking for us. Final thought: Right now, most companies are stuck in the “workslop” phase of AI adoption. They’re generating more content than ever but producing less clarity and value. The next phase will belong to organizations that stop chasing hype and start asking harder questions: What problem are we actually solving? Where does AI fit into that solution? Until then, we should be honest with ourselves: AI isn’t always speeding us up. Sometimes, it’s slowing everyone down. --- # 20+ Years as a CTO: Lessons I Learned the Hard Way URL: https://www.ivanturkovic.com/2025/09/14/cto-lessons-leadership-product-ai/ Published: 2025-09-14 Topics: AI, Career & Leadership, Software Engineering Being a CTO isn’t what it looks like from the outside. There are no capes, no magic formulas, and certainly no shortcuts. After more than two decades leading engineering teams, shipping products, and navigating the chaos of startups and scale-ups, I’ve realized that the real challenges and the real lessons aren’t technical. They’re human, strategic, and sometimes painfully simple. Here are the lessons that stuck with me, the ones I wish someone had told me when I started. Clarity beats speed every time Early in my career, I thought speed meant writing more code, faster. I would push engineers to “ship now,” measure velocity in lines of code or story points, and celebrate sprint completions. I was wrong. The real speed comes from clarity. Knowing exactly what problem you’re solving, who it matters to, and why it matters that’s what lets a team move fast. I’ve seen brilliant engineers grind for weeks only to realize they built the wrong thing. Fewer pivots, fewer surprises, and focus make a team truly fast. Engineers want to care, they just need context One of the most frustrating things I’ve witnessed is engineers shrugging at product decisions. “They just don’t care,” I thought. Until I realized: they do care. They want to make an impact. But when they don’t have context, the customer pain, the market reality, the business constraints,they can’t make informed decisions. Once I started sharing the “why,” not just the “what,” engagement skyrocketed. A well-informed team is a motivated team. Vision is a tactical tool, not a slogan I’ve been guilty of writing vision statements that sounded great on slides but did nothing in practice. The turning point came when I started treating vision as a tactical tool. Vision guides decisions in real time: Should we invest in this feature? Should we rewrite this component? When the team knows the north star, debates become productive, not paralyzing. Great engineers are problem solvers first I’ve worked with engineers who could write elegant code in their sleep, but struggle when the problem itself is unclear. The best engineers are not just builders, they’re problem solvers. My role as a CTO became ensuring the problem was well-understood, then stepping back. The magic happens when talent meets clarity. Bad culture whispers, it doesn’t shout I’ve learned to pay attention to the quiet. The subtle signs: meetings where no one speaks up, decisions made by guesswork, unspoken assumptions. These moments reveal more about culture than any HR survey ever could. Great culture doesn’t need fanfare. Bad culture hides in silence and it spreads faster than you think. Done is when the user wins Early on, “done” meant shipped. A feature went live, the ticket closed, everyone celebrated. But shipping doesn’t equal solving. Now, “done” only counts when the user’s problem is solved. I’ve had to unteach teams from thinking in terms of output and retrain them to think in terms of impact. It’s subtle, but transformative. Teams don’t magically become product-driven I used to blame teams for not thinking like product people. Then I realized the missing piece was me. Leadership must act like product thinking matters. Decisions, recognition, discussions, they all reinforce the mindset. Teams reflect the leadership’s priorities. Product debt kills momentum faster than tech debt I’ve chased the holy grail of perfect code only to watch teams get bogged down in building the wrong features. Clean architecture doesn’t save a product if no one wants it. Understanding the problem is far more important than obsessing over elegance. Focus is a leadership decision I once ran a team drowning in priorities. Tools, frameworks, and fancy prioritization systems didn’t help. The missing ingredient was leadership. Saying “no” to the wrong things, protecting focus, and consistently communicating what matters that’s what accelerates teams. Requirements are not the problem If engineers are stuck waiting for “better requirements,” don’t introduce another process. Lead differently. Engage with your team, clarify expectations, remove ambiguity, and give feedback in real time. Requirements are never the bottleneck leadership is. The hard-earned truth After twenty years, I’ve realized technology is the easy part. Leadership is where the real work and the real leverage lies. Clarity, context, vision, problem-solving, culture, focus these aren’t buzzwords. They are the forces that determine whether a team thrives or stalls. I’ve seen brilliant teams fail, and ordinary teams excel, all because of the way leadership showed up. And that’s the lesson I carry with me every day: if you want speed, impact, and results, start with the leadership that creates the conditions for them. Why AI won’t solve these problems With all the excitement around AI today, it’s tempting to think that tools can fix everything. Need better requirements? There’s AI. Struggling with design decisions? AI can suggest options. Want faster development? AI can generate code. Here’s the hard truth I’ve learned: none of these tools solve the real problems. AI can assist, accelerate, and automate but it cannot provide clarity, set vision, or foster a healthy culture. It doesn’t understand your users, your market, or your team’s dynamics. It can’t decide what’s important, or make trade-offs when priorities conflict. Those are human responsibilities, and they fall squarely on leadership. I’ve seen teams put too much faith in AI as a silver bullet, only to discover that the fundamental challenges alignment, focus, problem definition, and decision-making still exist. AI is powerful, but it’s a force multiplier, not a replacement. Without strong leadership, even the most advanced AI cannot prevent teams from building the wrong thing beautifully, or from stagnating in a quiet, passive culture. Ultimately, AI is a tool. Leadership is the strategy. And experience with decades of trial, error, and hard-won insight is what turns potential into real results. --- # Cocoa, Chocolate, and Why AI Still Can’t Discover URL: https://www.ivanturkovic.com/2025/09/12/cocoa-chocolate-and-why-ai-still-cant-discover/ Published: 2025-09-12 Topics: AI, Software Engineering Imagine standing in front of a freshly picked cocoa pod. You break it open, and inside you find a pale, sticky pulp with bitter seeds. Nothing looks edible, nothing smells particularly appetizing. By every reasonable measure, this is a dead end. Yet humanity somehow didn’t stop there. Someone, centuries ago, kept experimenting, steps that made no sense at the time: Picking out the seeds and letting them ferment until they grew mold. Washing and drying them for days, though still inedible. Roasting them into something crunchy, still bitter and strange. Grinding them into powder, which tasted worse. Finally, blending that bitterness with sugar and milk, turning waste into one of the most beloved foods in human history: chocolate. No algorithm would have told you to keep going after the first dozen failures. There was no logical stopping point, only curiosity, persistence, and maybe a bit of luck. The discovery of cocoa as food wasn’t the result of optimization, it was serendipity. Why This Matters for AI AI today is powerful at recombining, predicting, and optimizing. It can remix what already exists, generate new connections from vast data, and accelerate discoveries we’re already aiming toward. But there’s a limit: AI doesn’t (yet) explore dead ends with stubborn curiosity. It doesn’t waste time on paths that appear pointless. It doesn’t ferment bitter seeds and wait for mold to form, just to see if maybe, somehow, there’s something new hidden inside. Human discovery has always been messy, nonlinear, and often illogical. The journey from cocoa pod to chocolate shows that sometimes the only way to find the extraordinary is to persist through the ridiculous. The Future of Discovery If we want AI to go beyond optimization and into true discovery, it will need to embrace the irrational side of exploration, the willingness to try, fail, and continue without clear reasons. Until then, AI remains a tool for extending human knowledge, not replacing the strange, stubborn spark that drives us to turn bitter seeds into sweetness. Because the truth is: chocolate exists not because it was obvious, but because someone refused to stop at “nothing edible.” This path makes no sense. At every step the signal says stop. No data suggests you should continue. No optimization algorithm rewards the action. Yet someone did. And that’s how one of the world’s favorite foods was discovered. This is the gap between human discovery and AI today. AI can optimize, remix, predict. It can explore a search space, but only one that’s already defined. It can’t decide to push through meaningless, irrational steps where there’s no reason to keep going. It won’t follow a path that looks like failure after failure. It won’t persist in directions that appear to lead nowhere. But that’s exactly how discovery often works. Cocoa to chocolate wasn’t about efficiency. It was curiosity, stubbornness, and luck. The same applies to penicillin, vulcanized rubber, even electricity. Breakthroughs happen because someone ignored the “rational” stopping point. AI is far from that. Right now, it’s bounded by what already exists. It doesn’t yet invent entirely new domains the way humans stumble into them. The lesson? Discovery is still deeply human. And the future of AI will depend not just on making it smarter, but on making it willing to walk blind paths where no reward signal exists until something unexpected emerges. Because sometimes, you need to go through moldy seeds and bitterness to find chocolate. --- # When to Hire Real Engineers Instead of Freelancers for Your MVP URL: https://www.ivanturkovic.com/2025/09/08/hire-real-engineers-mvp/ Published: 2025-09-08 Topics: AI, Software Engineering Building a startup is a race against time. Every day you wait to ship your idea is a day your competitors could gain an edge. That’s why many founders start with freelancers or “vibe coding” to launch their MVP (Minimum Viable Product) quickly. But this fast-track approach comes with hidden risks. There comes a point when hiring real engineers is no longer optional, it’s critical for your startup’s survival. In this post, we’ll explore when it’s the right time to transition from freelancers to full-time engineers, and why vibe coding with low-cost freelancers can be dangerous for your MVP. Why Start With Freelancers? Freelancers are often the first choice for early-stage founders. Here’s why: Speed: Freelancers can help you quickly prototype your idea. Lower Cost: You pay for work done, without the overhead of full-time salaries or benefits. Flexibility: You can scale the workforce up or down depending on the project stage. Freelancers are perfect for validating your idea, testing market demand, or building proof-of-concept features. However, relying on freelancers too long can create technical debt and slow your growth when your product starts attracting real users. The Hidden Dangers of Vibe Coding With Low-Cost Freelancers Many founders are tempted by freelancers offering extremely low rates. While the idea of saving money is appealing, vibe coding with bargain-rate developers comes with serious risks: Poor Code Quality: Low-cost freelancers may cut corners, leaving messy, unmaintainable code. Lack of Documentation: Your codebase may be difficult for future engineers to understand or build upon. Delayed Timelines: Cheap freelancers often juggle multiple clients, causing unpredictable delays. False Confidence: Founders may assume their MVP is “production-ready” when it’s not. Hidden Costs: Fixing technical debt later often costs more than hiring quality engineers from the start. Using low-cost freelancers is fine for prototyping ideas quickly, but it becomes risky when your MVP starts attracting real users or paying customers. Signs You Need Real Engineers Here are the main indicators that your MVP has outgrown freelancers: 1. Product Complexity Increases Your MVP is no longer a simple prototype. Features require backend scalability, integrations, or complex logic. Codebase is hard for freelancers to maintain consistently. 2. Customers Expect Stability Paying users begin using your product regularly. Bugs, downtime, or inconsistent updates start hurting your credibility. You need reliable, professional code that can scale. 3. You Plan for Rapid Growth You anticipate increasing traffic, user engagement, or data volume. Your MVP needs a scalable architecture to handle more users efficiently. 4. Security and Compliance Matter Sensitive user data, payment systems, or regulatory requirements are involved. Freelancers may lack the expertise to ensure security best practices. How to Transition Smoothly to Full-Time Engineers Once you’ve decided to hire real engineers, plan the transition carefully to avoid disruption: Audit Existing Code: Identify areas of technical debt and create a roadmap for refactoring. Hire Strategically: Look for engineers with startup experience who can handle rapid iteration and product scaling. Document Everything: Ensure all features, APIs, and infrastructure are well-documented for the new team. Maintain Continuity: Keep a few top freelancers for short-term tasks during the handover period. Invest in Tools: Use code repositories, CI/CD pipelines, and testing frameworks to support professional development practices. Cost Considerations Hiring full-time engineers is an investment. While freelancers may seem cheaper upfront, consider the long-term costs: Technical Debt: Fixing poor-quality code can cost far more than hiring engineers initially. Lost Customers: Product instability can lead to churn and missed revenue. Opportunity Cost: Delays in scaling and adding features can let competitors win market share. Think of full-time engineers as insurance for your product’s future success. Conclusion Freelancers are invaluable for testing your idea and building a lean MVP quickly. But relying on low-cost vibe coding can be dangerous: messy code, delayed timelines, and hidden costs can stall your startup before it even takes off. Once your product gains traction, complexity, and paying users, hiring real engineers ensures stability, scalability, and long-term growth. Key Takeaway: Use freelancers for prototyping, but transition to full-time engineers before your MVP becomes a product your customers depend on. Planning the move carefully saves time, money, and frustration. Have you experienced the vibe code tax firsthand? Share your story in the comments and tell us how you decided when to hire full-time engineers. --- # Why “Lines of Code” Is the Wrong Way to Measure AI Productivity in Software Development URL: https://www.ivanturkovic.com/2025/08/25/why-lines-of-code-is-the-wrong-way-to-measure-ai-productivity-in-software-development/ Published: 2025-08-25 Topics: AI-Driven Development Last week, I had a conversation with another CTO that got me thinking about how we measure productivity in the age of AI-assisted software development. Here’s how it went: CTO: Right now, about 70% of our software output is generated by AI.Me: Interesting. How are you measuring that?CTO: By looking at the proportion of code that AI tools contribute during check-ins.Me: But what if the AI produces ten times more code than a human would to solve the same problem? Doesn’t that risk adding unnecessary complexity and future technical debt? That short exchange captures a much bigger problem: we are still in the earliest innings of understanding how AI fits into the software development lifecycle (SDLC). And one thing is already clear, measuring lines of code is not the way forward. The Myth of Lines of Code as a Metric The idea that "more lines of code = more productivity" has always been flawed. Good engineering has never been about who can write the most code. It's about solving problems in the most effective and maintainable way possible. A senior engineer might solve a business-critical feature in 20 lines of clean, tested code, while a less experienced developer or now, an AI might churn out 200 lines to accomplish the same thing. On paper, the latter looks "more productive," but in practice, they’ve just created more surface area for bugs, maintenance, and future refactoring. When AI is added into the mix, this risk multiplies. Large language models are excellent at producing code, but quantity is a misleading signal. We could easily end up with a lot of syntactically correct but architecturally messy software. Why AI-Generated Code Can Inflate the Wrong Numbers Imagine this scenario: Human developer approach Implements a payment validation service in 50 lines. Uses existing abstractions, adds unit tests, and ensures long-term maintainability. vs AI-generated approach Produces a 500-line solution with redundant logic, overly generic abstractions, and unnecessary boilerplate. Works today, but in 3 months, another engineer spends days cleaning it up. If you measure productivity by "lines of code checked in," the AI looks 10x more productive. But if you measure by long-term cost of ownership, the AI just created technical debt. The irony is that AI often defaults to verbosity. It tries to be "helpful" by filling in every possible branch, every possible configuration, every possible error case. That's useful in some contexts, but often it leads to bloat instead of clarity. Better Metrics for AI-Assisted Development If we agree that lines of code isn't the answer, what should we be looking at? Here are a few ideas: Cycle time to value How quickly can a feature go from idea → production → value delivered to the business? If AI speeds this up without adding long-term drag, that's true productivity. Code quality and maintainability Static analysis scores, cyclomatic complexity, duplication, and readability. AI should be measured on whether it improves or degrades these metrics. Bug rate and rework cost How often do AI-generated contributions introduce regressions? How much effort do humans spend debugging or rewriting AI code? Developer experience Are engineers spending less time on repetitive boilerplate? Do they feel empowered to focus on architecture, problem-solving, and innovation? Business outcomes Does AI accelerate delivery of meaningful features, reduce time-to-market, and improve customer satisfaction? Or does it just fill up the repo with more stuff to manage? Where This Leaves Us We are only at the beginning of figuring out how AI fits into modern software development. There will be new metrics, new practices, and new patterns of collaboration between humans and machines. But one thing is certain: lines of code is the wrong measure of productivity. In fact, if you see your AI adoption leading to more lines of code, you should pause and ask: Is this actually solving problems more effectively? Or are we just accumulating more technical debt at machine speed? The future of engineering with AI will belong to those who focus on outcomes, maintainability, and business value not vanity metrics. Conclusion AI is not going to replace developers; it's going to reshape what productivity looks like. The teams that succeed will be the ones that look beyond shallow metrics and focus on sustainable velocity. The next time someone tells you their AI is producing 70% of the code, ask them: 70% of what? Is that 70% value, or 70% debt? Because in software, less is often more. --- # On AI-Generated Code, Maintainability, and the Possibility of Disposable Software URL: https://www.ivanturkovic.com/2025/07/28/ai-generated-code-maintainability/ Published: 2025-07-28 Topics: AI, Software Engineering Over the past two years, I’ve been using various AI-assisted tools for programming like Codeium, GitHub Copilot, ChatGPT, and others. These tools have become part of my daily workflow. Mostly, I use them for code completion and to help me finish thoughts, suggest alternatives, or fill in repetitive boilerplate. I’m not doing anything too wild with autonomous agents or fully automated codebases. Just practical, incremental help. That said, even in this limited use, I’m starting to feel the friction. Not because the tools are bad but actually, they’re improving fast. Individual lines and even complete methods are cleaner than they used to be. The suggestions are smarter. The models are more context-aware. But one thing still nags at me: even with better completions, a lot of the output still isn’t good code in the long-term sense. Maintainability Still Matters The issue, to me, isn't whether AI can help me write code faster. It can. The issue is whether that code is going to survive over time. Is it going to be easy to understand, extend, or refactor? Does it follow a style or pattern that another human could step into and build on? This matters especially when you’re not the only one touching the code or when you come back to it after a few months and wonder, “Why did I do it this way?” And here’s the contradiction I keep running into: AI helps you write code faster, but it often creates more problems to maintain. That’s especially true when I’ve tested more advanced setups where you let an agent plan and generate entire components, classes, or services. It sounds great in theory, but in practice it causes a lot of changes, inconsistencies, and small bugs that end up being more trouble to fix than if I had just written it myself from the start. So for now, I stay close to completions. Code at the scale of a line or a method. It’s easier to understand, easier to control. I can be the architect, and the AI can be the assistant. The Self-Fulfilling Trap There’s a strange loop forming in AI development. Since the generated code is harder to reason about or maintain, people often treat it as throwaway. And because it’s throwaway, nobody bothers to make it better. So it stays bad. Self-fulfilling prophecy. The more AI you use to generate logic, the more you’re tempted to not go back and polish or structure it. You get into a loop of "just generate something that works," and soon you’re sitting on a pile of glue code and hacks that’s impossible to build on. But maybe that’s okay? Maybe we need to accept that some code isn’t meant to last. Disposable Software Might Be the Point This is where I’m starting to shift my thinking a little. I’ve always approached code as something you build on something that lives and evolves. But not all code needs that. A lot of software today already is disposable, even if we don’t admit it. One-off internal dashboards, ETL jobs, scripts for events, MVPs for marketing campaigns, integrations that won’t live beyond a quarter. We often pretend we’re building maintainable systems, but we’re not. We just don’t call them disposable. With AI in the mix, maybe it’s time to embrace disposability for what it is. Write the code, run the code, get the result, throw it away. Next time, generate it again maybe with better context or updated specs. This mindset removes a lot of the pressure around maintainability. And it fits the strengths of today’s AI tools. When Not to Use Agentic Systems (Yet) I’ve played with more autonomous agent systems what people call "agentic AI" or multi-agent code platforms. Honestly? I’m not sold on them yet. They generate too much. They make decisions I wouldn’t make. They refactor things that didn’t need to be touched. And then I spend more time reading diff views and undoing changes than I saved by delegating in the first place. Maybe in the future I’ll be comfortable letting an AI agent draft a service or plan out an architectural pattern. But today, I’m not there. I use these tools more like smart autocomplete than autonomous developers. It’s still very much my code and they’re just helping speed up the flow. Final Thoughts There’s a real risk of overhyping what AI can do for codebases today. But there’s also an opportunity to rethink how we treat different classes of software. We don’t need to hold everything to the same standards of longevity. Not every project needs to be built for 10 years of feature creep. Some software can (and should) be treated like scaffolding and built quickly, used once, and removed without guilt. And that’s where AI shines right now. Helping us build the things we don’t need to keep. I’ll keep experimenting. I’ll keep writing most of my own code, and using AI where it makes sense. But I’m also watching carefully because the balance between what’s worth maintaining and what’s better thrown away is shifting. And we should all be ready for what that means. --- # The AI isn’t going to be on call at 2 AM when things go down. URL: https://www.ivanturkovic.com/2025/05/22/llm-generated-code-liability/ Published: 2025-05-22 Topics: AI, Software Engineering Large Language Models (LLMs) like ChatGPT, Copilot, and others are becoming a regular part of software development. Many developers use them to write boilerplate code, help with unfamiliar syntax, or even generate whole modules. On the surface, it feels like a productivity boost. The work goes faster, the PRs are opened sooner, and there’s even time left for lunch. But there’s something underneath this speed, something we’re not talking about enough. The real issue with LLM-generated code is not that it helps us ship more code, faster. The real issue is liability. Code That Nobody Owns There’s a strange effect happening in teams using AI to generate code: nobody feels responsible for it. It’s like a piece of code just appeared in your codebase. Sure, someone clicked “accept,” but no one really thought through the consequences. This is not new, we saw the same thing with frameworks and compilers that generated code automatically. If no human wrote it, then no human cares deeply about maintaining or debugging it later. LLMs are like that, but on a massive scale. The "Average" Problem LLMs are trained on a massive corpus of public code. What they produce is a kind of rolling average of everything they’ve seen. That means the code they generate isn’t written with care or with deep understanding of your system. It’s not great code. It’s average code. And as more and more people use LLMs to write code, and that code becomes part of new training data, the model quality might even degrade over time, it becomes an average of an average. This is not just about style or design patterns. It affects how you: Deliver software Observe and monitor systems Debug real-world issues Write secure applications Handle private user data responsibly LLMs don’t truly understand these things. They don’t know what matters in your architecture, how your team works, or what your specific constraints are. They just parrot what’s most statistically likely to come next in the code. A Fast Start, Then a Wall So yes, LLMs speed up the easiest part of software engineering: writing code. But the hard parts remain: Understanding the domain Designing for change Testing edge cases Debugging production issues Keeping systems secure and maintainable over time These are the parts that hurt when the codebase grows and evolves. These are the parts where "fast" turns into fragile. Example: Generated Code Without Accountability Imagine you ask an LLM to generate a payment service. It might give you something that looks right, maybe even works with your Stripe keys or some basic error handling. But: What happens with race conditions? What if fraud detection fails silently? What if a user gets double-charged? Who is logging what? Is the payment idempotent? Is sensitive data like credit cards being exposed in logs? If no one really “owned” that code because it was mostly written by an AI and these questions might only surface after things go wrong. And in production, that can be very costly. So What's the Better Approach? LLMs can be great tools, especially for experienced engineers who treat them like assistants, not authors. To use LLMs responsibly in your team: Review AI-generated code with care. Assign clear ownership, even for generated components. Add context-specific tests and documentation. Educate your team on the why, not just the how. Make accountability a core part of your development process. Because in the end, you are shipping the product. The AI isn't going to be on call at 2 AM when things go down. Final Thoughts LLMs give us speed. But they don’t give us understanding, judgment, or ownership. If you treat them as shortcuts to ship more code, you may end up paying the price later. But if you treat them as a tool and keep responsibility where it belongs they can still be part of a healthy, sustainable development process. Thanks for reading. If you’ve seen this problem in your team or company, I’d love to hear how you’re dealing with it. --- # AI Isn’t Leveling the Playing Field, it’s Amplifying the Gap URL: https://www.ivanturkovic.com/2025/05/15/ai-widening-gap-juniors-vs-seniors/ Published: 2025-05-15 Topics: AI, Software Engineering We were told that AI would make development more accessible. That it would “level the playing field,” empower juniors, and help more people build great software. That’s not what I’m seeing. In reality, AI is widening the gap between junior and senior developers and fast. Seniors Are 10x-ing With AI For experienced engineers, AI tools like ChatGPT and GitHub Copilot are a multiplier. Why? Because they know: What to ask How to evaluate the answers What matters in their system How to refactor and harden code When to ignore the suggestion completely Seniors are using AI the same way a great chef uses a knife: faster, safer, more precise. Juniors Are Being Left Behind Many junior developers, especially those early in their careers, don’t yet have the experience to judge what’s good, bad, or dangerous. And here’s the issue: AI makes it look like they’re productive until it’s time to debug, optimize, or maintain the code. They’re often: Copy-pasting solutions without understanding the trade-offs Relying on AI to write tests they wouldn’t know how to write themselves Shipping code that works on the surface, but is fragile underneath What they’re building is a slow-burning fire of tech debt, and they don’t even see the smoke. Prompting Isn’t Engineering There’s a new kind of developer emerging: one who can write a great prompt but can’t explain a stack trace. That might sound harsh, but I’ve seen it first-hand. Without a foundation in problem-solving, architecture, debugging, and security prompting becomes a crutch, not a tool. Good engineering still requires: Judgment Pattern recognition Systems thinking Curiosity Accountability AI doesn’t teach these. Mentorship does. Where Is the Mentorship? In many teams, mentorship is already stretched thin. Now we’re adding AI to the mix, and some companies expect juniors to “just figure it out with ChatGPT.” That’s not how this works. The result? Juniors are missing the critical lessons that turn coding into engineering: Why things are built the way they are What trade-offs exist and why they matter How to debug a system under load When to break patterns How to think clearly under pressure No AI can give you that. You only get it from real experience and real guidance. What We Can Do If you’re a senior engineer, now is the time to lean into mentorship not pull away. Yes, AI helps you move faster. But if your team is growing and you’re not helping juniors grow too, you’re building speed on a weak foundation. If you’re a junior, use AI but don’t trust it blindly. Try to understand everything it gives you. Ask why. Break it. Fix it. Learn. Because here’s the truth: AI won’t make you a better engineer. But it will make great engineers even better. Don’t get left behind. Final Thoughts AI isn’t the enemy. But it’s not a shortcut to seniority either. We need to be honest about what it’s good for and where it’s failing us. Let’s stop pretending it’s a magic equalizer. It’s not. It’s a magnifier.If you’re already strong, it makes you stronger.If you’re still learning, it can hide your weaknesses until they blow up. --- # May 1st, Workers’ Day: Reflecting on the Future of Developers in the Age of AI URL: https://www.ivanturkovic.com/2025/05/01/workers-day-ai-future-developers/ Published: 2025-05-01 Topics: AI Every May 1st, we celebrate Workers' Day a moment to recognize the hard work, progress, and dignity of people across every profession. Traditionally, it’s a day to honor laborers, craftsmen, and knowledge workers alike. Today, in 2025, it’s worth asking: What does Workers' Day mean for developers, engineers, and tech builders when AI is rewriting the rules of work? Software development once a purely manual craft is being transformed. AI coding assistants, automated testing, and AI-driven architecture design are fundamentally reshaping what "developer work" looks like. This shift is both exciting and unsettling. And on a day meant to honor workers, it’s important to pause, reflect, and imagine the future of our profession. Developers and the Changing Nature of Work Writing software has historically been seen as highly skilled, highly manual labor an exercise in translating human logic and creativity into machine instructions. It’s an intellectual craft. But the arrival of advanced AI tools like GitHub Copilot, ChatGPT, and autonomous coding agents is shifting where the true value lies: Repetitive coding is being automated. Simple application patterns are being commoditized. Technical excellence is being augmented and sometimes replaced by strategic and creative excellence. Just as factory workers once faced automation during the industrial revolutions, developers today are facing an "intellectual automation" wave. The tasks that defined junior and even mid-level engineering roles are changing fast. AI Is Not Eliminating Developers. But It's Redefining Them There’s a common fear that AI will “replace” developers. That’s not exactly what's happening. Instead, AI is redefining the developer's role: From a pure code generator → to a system designer and critical thinker. From solving known problems → to exploring unknown problems. From typing faster → to asking better questions. The core of great software development will become less about how many lines of code you can write, and more about: How clearly you understand the problem. How creatively you design the solution. How effectively you validate assumptions with real users. In short: technical execution is being democratized, but insight, creativity, and judgment are becoming even more valuable. Workers' Rights and Respect in a Post-AI World Workers' Day isn’t just about jobs, it’s about dignity, fairness, and the value of human effort. As AI reshapes engineering, companies, leaders, and communities must ensure that: Developers are given opportunities to reskill and evolve. Workplaces value critical thinking and creativity over brute-force output. Workers are recognized for their judgment, leadership, and empathy, not just their typing speed. The dignity of developer work must evolve but it must not disappear. The risk is that companies, chasing efficiency, might treat developers like “AI supervisors” or “prompt engineers” only undervaluing the strategic, human essence of great engineering work. This May 1st, we should celebrate not just what developers do but how they think, imagine, and create. The Future of Developer Work: What Matters Now So, if you’re a developer, or leading a tech team, what skills and mindsets will matter most in the future? 1. Product ThinkingUnderstanding user needs, business goals, and the "why" behind the "what" will be essential. 2. Critical ReasoningKnowing when AI-generated code is wrong, incomplete, or misaligned with real-world requirements. 3. Creativity and InnovationThinking beyond what's obvious. AI can suggest patterns, but it can't invent bold new paradigms yet. 4. Collaboration and EmpathyWorking with designers, PMs, stakeholders, and users will become even more central. 5. Strategic Use of AIKnowing how to use AI, when to trust it, and when human judgment must take over. A New Kind of Craftsmanship In many ways, the evolution we’re living through isn't the end of craftsmanship in software, it's a renaissance. The tools have changed. The basic tasks have changed. But the core spirit of the developer the builder, the solver, the creator remains vital. If anything, AI makes the human parts of engineering even more valuable: Vision. Judgement. Ethical thinking. Originality. Connection to human needs. That’s worth celebrating this Workers' Day and fighting for in the years ahead. Final Thoughts: Building the Future Intentionally This May 1st, let's honor developers not for how fast they can code but for how thoughtfully they can shape the future. Let's champion workplaces that invest in human skills, not just AI tools. And let’s remember that while technology evolves, the heart of work the pursuit of meaning, the quest to make things better remains uniquely, beautifully human. Need help preparing your tech team for the AI era? I help companies and teams rethink their product strategies, evolve their engineering practices, and align AI innovation with real-world user needs. With 20+ years of experience across tech, fintech, and startup ecosystems, I can help your organization: Embrace AI smartly and strategically Foster product thinking across engineering Future-proof your development practices 📩 Contact me here or visit ivanturkovic.com to learn more. Let’s build a smarter, more human-centered future together. --- # AI Can Write Code. But Can It Build the Right Product? URL: https://www.ivanturkovic.com/2025/04/26/ai-coding-and-product-thinking/ Published: 2025-04-26 Topics: AI AI is changing how we write code. But there’s a critical question not enough teams are asking: Is it helping us build the right thing? From GitHub Copilot to ChatGPT and beyond, the latest AI tools are letting engineers generate code faster than ever before. But faster coding doesn’t automatically mean better outcomes. It just means we get to the wrong destination quicker unless we rethink how we build. If you’re a founder, CTO, engineer, or investor, this shift matters. Because in this new landscape, the real competitive edge isn’t AI-enhanced productivity. It’s clarity of vision, deep user understanding, and ruthless focus on solving the right problems. AI Code Generation: Speed Boost or Strategy Trap? The appeal of AI tools is obvious. With a few prompts or autocomplete suggestions, entire functions materialize. Repetitive boilerplate disappears. Developers feel empowered, moving quickly and staying in flow. But what are they building? The reality is: most teams don’t fail because they can’t ship fast enough. They fail because they’re solving problems no one cares about. “AI just helps you build the wrong thing faster if you’re not thinking critically about what matters.” In a sense, AI has become the new baseline. It’s like cloud infrastructure everyone has access to it. It’s not the edge. It’s the new floor. And once everyone has the same tools, what matters most isn’t how fast you build but what you choose to build. Why Product Thinking Beats Faster Coding Imagine two teams building similar features. One is using AI to crank out code at lightning speed. The other spends time talking to users, validating assumptions, and only writing code when it’s clear what matters most. Which one wins in the long run? The second team. Every time. Great companies aren’t made by productivity alone. They’re built by making smarter bets. There are already countless stories of AI-powered engineering orgs that pushed out entire platforms only to discover they’d misread the market or misunderstood the user. The cost of wrong decisions compounds faster when you’re building faster. Insight: Your AI stack isn’t your differentiator. Your user understanding is. Engineering Without Context Is a Risk and AI Just Amplifies It One of the most overlooked risks of AI in development is that it can further isolate engineers from the real-world problems their users are facing. Here’s how it plays out: AI makes coding easier and faster. Engineers get deeper into flow states. Less time is spent in discovery, user feedback, or product discussions. Features are built from assumptions, not insights. In short, AI widens the gap between builders and users, unless leaders intentionally close it. Your engineers don’t need to be product managers. But they do need context: Who are we solving for? What pain are we addressing? What does success look like for the user? Without that, they’re just solving technical puzzles. And AI makes that even easier to do without questioning the why. AI Strategy Is Not Product Vision: Lessons for CTOs and Founders In 2025, one of the most common strategic mistakes I see from tech leadership is confusing “having an AI strategy” with “having a product vision.” CTOs and founders proudly proclaim their AI initiatives, touting LLM integrations and Copilot productivity gains. But often, the product itself lacks a core value proposition. There's no unique insight. No user obsession. Just surface-level tech hype. Warning sign: If your AI roadmap is longer than your product roadmap, you’re probably heading in the wrong direction. AI should support your vision, not become it. It’s a tool to accelerate clarity not a substitute for it. How Smart Teams Use AI in Product Development So how should high-performing teams approach AI? They don’t ignore it. But they use it intentionally, not reactively. Here’s how they do it: Start with product clarity: AI gets plugged in after the problem is well understood. Use AI to prototype, not finalize: It’s a great tool for drafts, ideas, and quick iterations, but not for skipping over design thinking. Build user feedback loops into engineering: Teams that talk to users frequently make better use of AI because they know what’s actually needed. Avoid cargo culting AI features: Just because you can plug in an LLM doesn’t mean you should. A simple framework for alignment: Write a 1-page problem brief before building. Identify the user pain, not the feature. Validate with a real customer, not a teammate. Use AI to build faster once the path is clear. What Investors Should Really Look For If you’re an investor evaluating AI-native startups, this part is for you. Don’t just look at AI usage. That’s table stakes. Instead, dig deeper: Are they solving a real user problem with a compelling insight? Is the team obsessed with learning, not just launching? Are engineers looped into product and customer conversations? Is AI being used for leverage, or as a distraction? The best startups in the AI era won’t be those with the flashiest demos. They’ll be the ones with: Small teams. Sharp thinking. Deep user insights. Relentless focus. In an AI World, Thinking Is Your Edge AI is raising the average. It’s making every developer a little more productive. It’s removing drudge work and unlocking new workflows. But it’s not a silver bullet. The teams that will truly win in this era are the ones that combine AI with critical thinking, product obsession, and strategic clarity. They’ll ask better questions. They’ll build less but better. And they’ll move faster because they know exactly where they’re going. So, how is your team using AI?To generate more code or to make smarter decisions about what to build in the first place? Like this post?🔁 Share it with a founder or CTO who’s too hyped about AI.💬 Drop a comment or DM me with how your team is integrating product thinking into AI workflows.📩 Subscribe for more thoughts on tech, product, and the future of software. Let’s Build Smarter, Not Just Faster AI isn’t going away. But how we use it and what we choose to build with it will define which teams win and which ones waste time. If you’re a founder, CTO, or product leader trying to navigate this new AI-powered world, and you're serious about building the right product, I can help. I bring over 20 years of experience across fintech, crypto, payments, and startup ecosystems as a developer, CTO, and product strategist. I’ve helped teams go from messy ideas to crystal-clear roadmaps and scalable platforms. 🔧 What I help with: Defining product strategy in AI-heavy contexts Aligning engineering teams with real user needs Avoiding technical overbuild and feature bloat Building lean, fast-moving MVPs with Ruby on Rails and modern stacks Coaching tech teams to think product-first, code-second If you want clarity, speed, and smarter decisions baked into your product and engineering culture, let’s talk. 📩 Get in touch with me or visit ivanturkovic.com to learn more. Let’s build something that matters. --- # AI “Vibe” Coding Will Increase Demand for Software Engineers; Here’s Why URL: https://www.ivanturkovic.com/2025/04/15/ai-coding-demand-software-engineers/ Published: 2025-04-15 Topics: AI Today, my LinkedIn feed was overflowing with hot takes about AI and the future of programming. A recurring theme? That AI will make software engineers obsolete. No-code platforms, AI-assisted builders, and vibe-based coding were all being hailed as the future. Here’s my take:AI is about to increase the demand for software engineers — not replace them. And we’ve seen this kind of thing before. The Swedish Renovation Effect Years ago in Sweden, a popular TV show demonstrated how easy it was to renovate your own house. Enthused by what they saw, thousands of Swedes began renovating their homes on their own. The result?Disaster. Half-done kitchens. Electrical fires. Poorly installed plumbing. And what followed was a massive surge in demand for professional carpenters, electricians, and contractors. AI-powered programming is heading down the same path. We’re about to see lots of excited builders — and just as many messes to clean up. What Is “Vibe” Coding? “Vibe” coding is the idea that you can build software by simply describing what you want in natural language. “Make me an app that helps me track my fitness and suggests recipes.” And boom — AI tools like ChatGPT, GitHub Copilot, or Replit Ghostwriter produce working code. But building something functional isn’t the same as building something reliable. AI can’t: Ensure secure architecture Integrate across complex systems Handle scaling issues Manage technical debt Think critically about edge cases and long-term impact That’s where skilled software engineers step in. Real Examples of Where Engineers Will Still Be Needed Let’s go through three common scenarios that show why engineers aren’t going anywhere. 1. The Startup Founder MVP A non-technical founder uses AI to create a prototype. It works and gains users. But now they need: API design that scales Data security and privacy compliance Frontend polish and accessibility DevOps for deployment pipelines and monitoring They’ll be hiring engineers soon. 2. The Corporate DIY Tool A marketing team builds an internal dashboard with AI. It’s fast — until: Traffic grows and it starts crashing Security holes appear It can’t integrate with enterprise systems Enter: The IT and engineering team to fix and rebuild it. 3. The Indie Hacker Problem An indie developer builds a cool tool with AI and gets traction. Now users want new features. Stripe integration is flaky. Bugs start piling up.Suddenly, vibe coding hits its limits — and real development work begins. Just Like Excel… But Bigger We’ve seen this before with Excel. Millions use it for complex calculations, planning, and even pseudo-apps. But when the stakes rise — in finance, logistics, or reporting — companies bring in: Excel consultants VBA programmers Business analysts The same will happen with AI-built software. It’ll democratize access, but it’ll also raise expectations. Engineers Will Be the New Advisors, Builders, and Maintainers Software engineers in the AI age won’t just code. They’ll: Audit AI-generated systems Refactor MVPs into production-ready platforms Coach teams on maintainability and design thinking Design better APIs and AI integration points Create guardrails and tooling that make AI-generated code safer and smarter In other words: we’ll be more important than ever. AI Is the New Hammer — But You Still Need Builders AI coding tools are like giving hammers to the world. Some people will build amazing things. Others will build something that looks good but falls apart in the rain. And just like the Swedish renovation boom, someone will need to fix, maintain, and scale all that newly built infrastructure. That someone is you — the software engineer. About the Author Ivan Turkovic is a seasoned tech expert, fintech and blockchain platform architect, and former CTO of multiple startups. With 20+ years of experience building products and teams using Ruby on Rails, TypeScript, and cloud-native platforms, he helps businesses turn complex systems into reliable, scalable solutions. Whether you’re an early-stage founder, a company struggling with technical debt, or a team trying to level up your AI integration strategy — Ivan can help. 🖥️ Visit ivanturkovic.com for more articles, insights, and contact info📧 Reach out via LinkedIn or email ivan.turkovic@gmail.com --- # The Age of AI: Why Experienced Tech Architects and CTOs Are More Crucial Than Ever URL: https://www.ivanturkovic.com/2025/04/11/ai-product-design-experienced-ctos-importance/ Published: 2025-04-11 Topics: AI Artificial intelligence is transforming industries at an unprecedented pace, redefining the way we design, develop, and deploy products. However, with great power comes great responsibility. As AI automates more processes and decision-making, the need for thoughtful product design, robust security, and meticulous attention to edge cases has never been more critical. While AI can handle vast amounts of data and make predictions, it lacks human intuition. The real challenge for companies leveraging AI is ensuring that the technology is implemented in a way that minimizes risks while maximizing efficiency and accuracy. This is where experienced tech architects and CTOs come in. Their deep understanding of system design, security, and data modeling is becoming a key differentiator in creating AI-powered products that are reliable and resilient. The Increasing Complexity of AI-Driven Product Design Unlike traditional software, AI-driven products require a fundamentally different approach to design and development. Instead of writing explicit rules for every scenario, AI models learn from data, which introduces new challenges in predicting behavior, handling unexpected cases, and preventing security vulnerabilities. One of the biggest challenges is handling edge cases. AI models are trained on data, but real-world applications often introduce unexpected situations that weren’t part of the training set. A lack of foresight in handling these cases can lead to significant issues. Consider these examples: Self-driving cars: AI systems are trained on millions of traffic scenarios, but rare or unusual events (like an overturned truck or a person walking with an unusual posture) can confuse the system. Tesla’s Autopilot has been criticized for failing in such edge cases, sometimes leading to accidents.AI chatbots: Microsoft’s Tay AI was released in 2016 and quickly turned into a PR disaster when users manipulated it into making racist and offensive statements. The lack of robust content moderation mechanisms exposed the bot’s vulnerability to adversarial manipulation.Healthcare AI: A medical AI model trained primarily on data from Western countries may perform poorly when deployed in regions with different demographic data, leading to incorrect diagnoses or biased treatment recommendations. To prevent such failures, experienced architects must proactively model possible failure scenarios and ensure that fallback mechanisms are in place. Anticipating these issues requires deep knowledge of system architecture and an understanding of human behavior—qualities that experienced CTOs and technical leaders bring to the table. Data Modeling: The Foundation of AI Success AI systems are only as good as the data they are trained on. Poorly modeled data can introduce biases, inaccuracies, and unpredictable behaviors. This is another reason why experienced tech architects are invaluable. Bias in AI systems: Amazon once had to scrap an AI-powered recruiting tool that discriminated against women because it was trained on past hiring data, which was predominantly male. An experienced AI architect would have identified this risk and designed the system to counteract historical biases.Data drift: AI models degrade over time as real-world data changes. If data pipelines aren’t continuously monitored and updated, performance will decline. Google’s AI for identifying diabetic retinopathy struggled when deployed in real-world clinics because the image quality was lower than in its training dataset.Scalability challenges: AI models that work well in development often fail at scale due to inefficient data pipelines. A well-designed architecture ensures that data ingestion, preprocessing, and storage can handle increasing loads without performance bottlenecks. Tech architects who understand data engineering, pipelines, and real-time processing can build more resilient AI systems that stand the test of time. Security and Data Privacy: A Major Concern in AI Systems One of the biggest risks with AI-driven systems is security and data privacy leaks. Companies without experienced leadership often underestimate the attack surface that AI systems create. Some high-profile failures include: Samsung’s AI mishap (2023): Employees used ChatGPT for internal coding assistance, accidentally leaking sensitive source code. The lack of internal security policies and oversight allowed this breach to happen.Deepfake abuse: AI-generated deepfakes have been used for identity fraud, political misinformation, and even scams impersonating executives. Companies need AI-specific security measures to detect and prevent such misuse.GDPR violations: AI models that store or process personal data without clear consent can lead to massive fines. Meta (Facebook) has faced repeated regulatory scrutiny for mishandling user data. Experienced CTOs and security-focused architects play a vital role in identifying potential AI security risks before they become major breaches. This includes designing secure data pipelines, implementing differential privacy techniques, and ensuring AI models do not memorize sensitive information. Why Experienced CTOs and Tech Architects Will Thrive AI is reducing the need for repetitive coding, but it is increasing the demand for high-level system thinking, security awareness, and strategic planning. Companies that blindly rely on AI without understanding its risks are setting themselves up for failure. The future belongs to tech leaders who can: Design AI-powered systems that handle edge cases gracefully.Build scalable and unbiased data models.Prioritize security and data privacy in every AI-driven product. While junior developers and AI automation can accelerate coding and prototyping, only experienced architects can prevent catastrophic failures before they happen. As AI continues to reshape industries, those with deep technical expertise will be in higher demand than ever. If you're a CTO or tech architect, now is the time to double down on your expertise. AI is not replacing your role—it's making it more valuable than ever. Looking for Expert Help? Let’s Work Together! If you need expert guidance to bring your ideas to life, I’m here to help. Whether it's building innovative solutions, refining your tech strategy, or tackling complex challenges, let’s connect and create something great. Reach out today on ivan.turkovic@gmail.com, and let’s make things happen! --- # The Inevitable Churn of AI-Powered Development Platforms URL: https://www.ivanturkovic.com/2025/04/07/ai-development-churn-expectations-vs-reality/ Published: 2025-04-07 Topics: AI AI-powered development tools like Lovable, Bolt, and others have captured the imagination of developers and non-developers alike. The promise? Build complete applications with just a few prompts. The reality? A much harsher learning curve, hidden complexities, and an eventual realization that these tools, while powerful, are not yet capable of fully replacing traditional software engineering. The Hype: Why AI-Powered Development Feels Revolutionary There's a reason why so many are flocking to AI-powered coding platforms. They offer something unprecedented—turning natural language descriptions into working code, reducing development time, and making software engineering more accessible to those without deep programming knowledge. For a while, it seems magical. With just a few prompts, a prototype can be generated, UI components materialize, and APIs are wired up. For solo entrepreneurs, product managers, and designers who have always relied on engineers to bring their ideas to life, AI-powered development tools feel like an emancipation. They provide the illusion of democratization, allowing anyone to create software—until they hit the brick wall of reality. The Reality: Why These Tools Are Not Enough (Yet) Building a functional app is not just about writing code. It involves architecture, performance optimization, security, state management, backend integrations, database design, debugging, and deployment. These aspects of software development are where AI-generated code often struggles or outright fails. Many, myself included, have tried to build and deploy simple applications using these AI tools, only to run into major roadblocks: Database Connection Issues: AI-generated code frequently struggles with database connections, especially when dealing with cloud environments, ORMs, or different types of data persistence strategies.Authentication & Security Concerns: Many platforms generate basic authentication flows, but real-world implementations require fine-tuning for access control, session management, and compliance with security standards.API Integrations & Rate Limits: AI may generate API calls, but it doesn’t always handle edge cases, pagination, throttling, or error responses properly.Frontend Hydration & State Management: AI-generated frontend code often runs into hydration errors, especially in React or other component-based frameworks.CORS Policy Errors & DevOps Challenges: Cross-Origin Resource Sharing (CORS) issues plague AI-generated projects, requiring manual intervention. Similarly, deployment is far from a one-click experience, as infrastructure knowledge is often required. These problems aren’t just annoyances; they are project killers for those without the technical expertise to debug them. Why Churn is Inevitable Many people jumping into AI-powered development tools do so because of FOMO (Fear of Missing Out). They see impressive demos and believe they can bypass years of software engineering experience. However, after a few frustrating attempts, reality sets in. Without a foundational understanding of software engineering principles, many will abandon these tools entirely. Mismatched Expectations The expectation is that AI will do everything for them. The reality is that AI can accelerate certain aspects of development but cannot (yet) replace the problem-solving skills of an experienced developer. This gap between expectation and reality inevitably leads to frustration and churn. Lack of Debugging & Support Unlike traditional development, where countless Stack Overflow threads, GitHub issues, and community discussions exist, AI-generated code can be unpredictable. Debugging issues with AI-generated code often requires real software engineering skills, something many early adopters of these tools do not have. Dependency on Experts In my own experience, I only got past these obstacles because I had access to people who actually understand software engineering. Many others won't have that same support network, making it even more frustrating when things don’t work. The Future of AI-Powered Development Despite these challenges, I’m still building with AI and learning a ton. AI-assisted development is undoubtedly the future—but it’s not the present solution many believe it to be. Here’s what needs to happen before these tools can truly democratize software development: Better Abstraction of Complexity: AI tools need to handle real-world complexities like authentication, database management, and security without requiring deep expertise from users.Improved Debugging & Documentation: There must be AI-assisted debugging and more robust documentation around generated code.Integration with Traditional Development Workflows: Instead of aiming to replace engineers, AI tools should become better copilots that assist rather than automate everything. AI-powered development will continue to evolve, but the current wave of enthusiasm will likely be followed by a period of disillusionment. Many will churn out of frustration, while others—especially those willing to learn and adapt—will reap the benefits of being early adopters. For now, AI-generated code is a powerful tool, but not a replacement for the art and science of software engineering. The hype is real, but so are the limitations. Those who acknowledge and navigate these challenges will be the ones who truly benefit from this technological shift. --- # AI vs. Experience: The Real Cost of Knowing What Works URL: https://www.ivanturkovic.com/2025/03/31/ai-vs-experience-coding-mistakes/ Published: 2025-03-31 Topics: AI Copy and Paste from AI: $20 Actually Knowing What's Going to Work: $150,000 Knowing Where to Place It: Priceless The rise of AI-powered tools has made it easier than ever for people to generate code, write content, and create designs with minimal effort. Platforms like ChatGPT, Copilot, and other AI-driven assistants can spit out working solutions in seconds. But while AI can provide fast results, there’s an undeniable gap between “having code that runs” and “having code that works in the real world.” Just because something compiles, doesn’t mean it scales. Just because it executes, doesn’t mean it’s secure. And just because AI gives you a function, doesn’t mean it’s the right one for your specific problem. This is the difference between merely copying and pasting code and actually knowing what will work—and more importantly, where to place it. In this post, we'll explore why experience is irreplaceable, the hidden costs of not knowing what you're doing, and examples of how things can go wrong when you rely solely on AI without the expertise to back it up. The Trap of "It Works, So It's Fine" One of the biggest pitfalls for beginners relying on AI-generated code is assuming that if something works, it must be correct. AI can generate code that runs, but without experience, it’s easy to overlook issues like: Security vulnerabilitiesPerformance bottlenecksMaintainability nightmaresScalability limitationsLegal and compliance risks Here are some real-world examples where blindly trusting AI (or generic code from the internet) can lead to disaster. Example 1: SQL Injection – The Classic Security Oversight Let's say you ask an AI to generate a login system in Python using Flask and SQLite. You get something like this: @app.route('/login', methods=['POST']) def login(): username = request.form['username'] password = request.form['password'] query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'" result = db.execute(query).fetchone() if result: return "Login successful" return "Invalid credentials" It works! You enter a username and password, and it checks if they exist in the database. But here’s the problem—this code is vulnerable to SQL injection. Anyone with basic knowledge of security can enter a malicious string like: ' OR '1'='1' -- And suddenly, they’re logged in as an admin. An experienced developer would know that SQL queries should always use parameterized statements: query = "SELECT * FROM users WHERE username = ? AND password = ?" result = db.execute(query, (username, password)).fetchone() But if you don’t know what to look for, you wouldn’t even realize there’s a problem—until it’s too late. Example 2: "Just Use This AI-Generated Smart Contract" With blockchain and smart contracts becoming more popular, AI can generate Solidity contracts for you. But Solidity is tricky—one wrong move and you could introduce vulnerabilities that cost millions. For example, imagine an AI generates this Solidity contract: contract SimpleBank { mapping(address => uint256) public balances; function deposit() public payable { balances[msg.sender] += msg.value; } function withdraw(uint256 amount) public { require(balances[msg.sender] >= amount); payable(msg.sender).transfer(amount); balances[msg.sender] -= amount; } } It looks fine, right? But this contract is vulnerable to reentrancy attacks. A malicious actor can drain the contract using a recursive function call before the balance updates. An experienced blockchain developer would immediately see this issue and fix it by using the checks-effects-interactions pattern or a reentrancy guard. Example 3: The Nightmare of Scaling Bad Code An AI can generate a sorting algorithm for you. But let’s say you need to process millions of data points efficiently. An AI might give you something like: def sort_list(lst): return sorted(lst) For small datasets, this works fine. But what if you're sorting billions of records? An experienced developer would ask: What’s the computational complexity of this approach?Is there a more efficient algorithm like Merge Sort, Quick Sort, or even a parallelized approach?How does this impact memory usage?Should I use a database index instead of sorting in memory? AI won’t always make those considerations for you. Without experience, you might not even realize there’s a problem—until your system grinds to a halt under real-world loads. Why Experience Is Priceless The AI tools available today are powerful, but they are not a replacement for deep expertise. The ability to: Anticipate edge casesUnderstand security implicationsOptimize for performance and scalabilityWrite maintainable codeKnow the right architecture for the problem …is something that only comes with experience. AI can be an incredible assistant, but it won’t teach you intuition. It won’t tell you what’s actually going to work in production. It won’t warn you when something is subtly broken. --- # No Easy Coding in AI: The Most Common Mistakes and How to Improve Yourself URL: https://www.ivanturkovic.com/2025/03/30/best-practices-ai-assisted-coding/ Published: 2025-03-30 Topics: AI Artificial Intelligence (AI) has revolutionized software development, making it faster and more efficient. However, many developers still struggle with AI coding tools, often making mistakes that lead to frustration, inefficiencies, and broken codebases. In this guide, we'll explore the best practices for AI-assisted coding, focusing on the most common mistakes and how to improve your workflow. 1. No Planning Jumping straight into coding without a plan is a recipe for disaster. A well-thought-out plan can save hours of debugging and restructuring later. My Planning Hack I turn on ChatGPT voice and have a one-on-one conversation about what I want to build. After 15 minutes, I ask: "Write me a well-structured draft on all the things we've finalized in this conversation." This way, I use AI as my: Brainstorming buddyCritiqueWeb researcherDraft writer The result? A one-page document outlining the core features of my MVP. Don’t build blindly. Plan before you hunt. 2. No Knowledge Base for AI Models AI coding models work best when they have a reference point. Reducing hallucinations and irrelevant code generation requires a structured knowledge base. How to Build a Knowledge Base for AI Coding Models After drafting my idea, I create structured documentation: Product Requirements Document (PRD) – What the app doesApp Flow Document – How users interact with the appTech Stack Document – The technologies being usedFrontend Guidelines – UI/UX rulesBackend Structure – API architecture and database schema This documentation acts as a reference, ensuring AI models generate relevant and accurate code. Learn more about AI Documentation 3. Not Picking the Right Tools Every AI coding tool has its own superpower. Choosing the wrong tool can lead to inefficiencies. AI Coding Tool Comparison: ToolStrengthsWeaknessesChatGPT-4General-purpose coding, debugging, writing docsMay hallucinate in complex logicClaude 3.5Excellent for writing and structuring codeNot great for debuggingGemini Flash 2.0Scanning and refactoring large codebasesLacks execution speedCursor/WindsurfAI-powered IDE for smart code executionNeeds AI-specific setup Check out the best AI coding tools 4. Not Picking the Right Tech Stack AI models are trained on specific programming languages. Using the right tech stack leads to better quality code with fewer errors. AI-Friendly Tech Stacks Frontend Next.jsViteFlask (for Python-based projects) Database Supabase (PostgreSQL)Firebase Authentication Clerk.devSupabaseFirebase AI Models OpenAIClaudeGemini Best AI-Compatible Programming Languages LanguageAI Model EffectivenessPythonExcellentJavaScript/TypeScriptGreatRustDecentRubyLimited (but improving) 5. Not Building Step by Step Letting AI plan the entire development process often results in messy, unstructured code. The Fix: My 60-Step Implementation Plan Instead of letting AI decide everything, I am creating a step-by-step execution plan with 60+ checkpoints to ensure nothing gets skipped. AI should only be used to execute the plan, not to decide what to do next. 6. No Debug Prompting Debugging AI-generated code can be painful. Use these techniques to improve debugging: Attach the error and say: "Use chain of thought reasoning to find the core issue first and then plan step by step to fix the issue."Ask AI to "follow best coding practices. Search the web and find the fix for this issue."Only attach relevant files to improve AI’s focus. 7. Not Using Multiple AI Models No single AI model can do everything. Use different models for different scenarios: In Cursor/Windsurf: Claude Sonnet 3.5 → Best for writing and executing codeGPT-4 o1/o3-mini-high → Best for debugging complex errorsGemini Flash 2.0 → Best for scanning and updating entire codebases 8. Not Using Starter Kits Why start from scratch every time? Use boilerplates to speed up development and reduce errors. I am preparing 4+ pre-built boilerplates specifically designed for AI coding to reduce token usage and avoid unnecessary errors. 9. Quitting Too Early AI coding is exciting at first, but frustration kicks in when errors start piling up. Expect hundreds of build issues and AI mess-ups. The Solution: Strong Foundation → Build rules and documentation to keep AI coding structuredVersion Control → Use Git to manage source code changes effectively Learn more about Git version control Final Thoughts AI-assisted coding is powerful, but only if used correctly. By avoiding these common mistakes and applying structured workflows, you can build faster, smarter, and more efficient applications. Plan before you code. Structure before you execute. Debug with intelligence. Adapt and improve. --- # Can AI Replace Junior Developers, or Should We Train Them to Become Seniors? URL: https://www.ivanturkovic.com/2025/03/27/can-ai-replace-junior-developers-or-should-we-train-them-to-become-seniors/ Published: 2025-03-27 Topics: AI The rapid advancement of AI has sparked an ongoing debate in software development: Should companies replace junior developers with AI-powered tools and a few senior engineers, or should they invest in training juniors to become future seniors while leveraging AI to enhance their productivity? While both perspectives have merit, the ideal solution is often a balance. A well-structured team that combines the fresh enthusiasm of junior developers with the experience of senior engineers, amplified by AI, can outperform any one-dimensional approach. The Case for AI Replacing Junior Developers AI-powered coding assistants like GitHub Copilot, Tabnine, and ChatGPT have dramatically improved code generation, debugging, and even architectural decision-making. These tools allow senior developers to move faster and reduce the need for a large number of junior developers. Real-Life Example: Stripe Stripe, a leading fintech company, experimented with reducing its reliance on junior developers by leveraging AI-powered development tools. They streamlined their engineering teams, allowing senior engineers to complete tasks in half the time with AI assisting in boilerplate code, debugging, and automated testing. This helped Stripe reduce costs while maintaining high-quality output. Pros: Lower payroll costsFaster development cyclesFewer mistakes and more consistent code quality Cons: No pipeline for future senior developersLoss of diverse perspectives and innovationHigher risk if the senior developer leaves The Case for Training Junior Developers On the other hand, investing in junior developers creates long-term sustainability and cultivates a strong company culture. AI should be seen as a tool to accelerate their learning rather than a replacement. Real-Life Example: Shopify Shopify has a strong internal development culture that emphasizes mentorship and junior developer growth. They implemented a program where junior developers used AI tools to assist with coding, debugging, and understanding best practices. Within two years, many juniors had transitioned to mid-level roles, reducing hiring costs and increasing team loyalty. Pros: Builds a pipeline of future senior engineersEncourages innovation and fresh perspectivesCreates a collaborative and diverse work environment Cons: Higher initial training costsSlower development speed in the short termRisk of juniors making mistakes early on The Ideal Team: A Mix of Juniors, Seniors, and AI The best-performing teams strike a balance between juniors, seniors, and AI tools. Here’s why: Juniors bring fresh ideas and enthusiasm but lack deep experience.Seniors provide technical leadership and decision-making skills.AI accelerates productivity and helps fill knowledge gaps. Example: The Hybrid Approach at Supercell Supercell, a gaming startup, faced a dilemma: Hire more juniors, bring in an expensive senior team, or leverage AI? They opted for a hybrid approach: A few senior developers focused on core architecture and mentoring.Juniors were onboarded with structured training programs and AI-powered coding tools.AI helped automate repetitive tasks, reducing junior mistakes. After a year, productivity was up 40%, and juniors were significantly more skilled. How to Start Implementing This Approach Invest in AI Tools – Provide teams with AI-powered coding assistants, but don’t rely on them blindly.Create a Structured Training Program – Develop mentorship programs where seniors guide juniors.Encourage AI-Assisted Learning – Let juniors use AI for debugging and code explanations to accelerate learning.Pair Programming and Code Reviews – Ensure juniors work alongside seniors to absorb knowledge.Set Clear Growth Paths – Provide juniors with clear milestones for career advancement. Conclusion Rather than replacing junior developers with AI, the smarter approach is to use AI to enhance their growth. Companies that cultivate junior talent will be better positioned for long-term success, while AI can help both juniors and seniors become more productive. By striking the right balance between automation and human development, teams can maximize efficiency, innovation, and sustainability in the ever-evolving tech landscape. --- # When Product Owners Ignore Security: Why Experienced Developers Matter URL: https://www.ivanturkovic.com/2025/03/24/when-product-owners-ignore-security-why-experienced-developers-matter/ Published: 2025-03-24 Topics: AI In the modern tech landscape, product owners often assume that software will "just work" safely. But security isn't automatic, and a lack of foundational understanding can lead to serious vulnerabilities. While AI has become a valuable tool in development, it cannot replace the expertise of seasoned developers who can anticipate, diagnose, and resolve complex security flaws. In this article, we'll explore why relying solely on assumptions—or even AI—can put users at risk, and how experienced developers have rescued failing projects from disaster. The Illusion of Safety in Software Development Product owners, especially those without technical backgrounds, often focus on features, user experience, and business objectives. Security tends to be an afterthought—if it’s considered at all. They might assume: "Our framework handles security automatically.""AI-generated code must be secure.""Hackers won’t target us because we’re a small company." These assumptions are dangerous. Security must be deliberately designed into a system, and failing to do so can lead to catastrophic breaches. Real-World Examples of Projects Gone Wrong (And How Developers Saved Them) 1. The E-Commerce Disaster: Exposed Customer Data A startup built an e-commerce platform using a popular low-code solution. The product owner assumed that security was baked into the platform. What they didn’t realize was that their database had been left publicly accessible, exposing thousands of customers' personal and payment data. How an Experienced Developer Saved It: A senior developer was brought in after an independent security researcher exposed the vulnerability. The developer: Implemented proper authentication and access controls.Added encryption for stored customer data.Set up automated security testing to prevent future leaks. Without this intervention, the company would have faced lawsuits and lost customer trust. 2. The AI Code Generator Mistake: Unsecured API Keys A SaaS company decided to speed up development using AI-generated code. Their AI assistant provided clean-looking API integrations, but it didn’t consider security best practices. The AI-generated code stored API keys directly in the frontend, exposing them to anyone who inspected the browser’s developer tools. How an Experienced Developer Saved It: A security-conscious engineer audited the code and: Moved API keys to a secure environment variable setup.Implemented proper authentication (OAuth2) instead of static keys.Set up monitoring alerts for unusual API usage. The fix prevented potential attackers from hijacking API calls and exploiting user data. 3. The Blockchain Nightmare: A Flawed Smart Contract A startup launched a DeFi project, assuming that a popular AI tool could generate Solidity smart contracts with minimal oversight. The result? A flawed contract that allowed attackers to drain funds due to a reentrancy bug. How an Experienced Developer Saved It: After losing some initial funds, they hired a blockchain security expert who: Refactored the smart contract using best practices.Implemented reentrancy guards to prevent exploit attempts.Conducted thorough security audits before redeploying. Without an expert, the project could have been completely compromised. AI is a Tool, Not a Replacement for Expertise AI-generated code can be helpful, but it lacks real-world context. AI doesn’t understand business logic, legal requirements, or evolving security threats. An experienced developer: Thinks critically about how code will be used (and misused).Understands regulatory compliance (GDPR, PCI-DSS, HIPAA, etc.).Can adapt and apply security measures based on the specific application. The Takeaway: Invest in Expertise Before It’s Too Late Security isn’t something that should be bolted on at the end of a project—it should be a core consideration from the start. Product owners must: Consult experienced developers early in the development cycle.Prioritize security just as much as features and user experience.Recognize that AI is a helpful assistant, but not a substitute for human expertise. Ignoring security can lead to costly breaches, reputational damage, and legal consequences. But with the right developers involved, businesses can build not just functional, but also secure, reliable, and scalable software. Have You Seen Security Mistakes Firsthand? What’s the worst security oversight you’ve encountered in a project? Share your experiences in the comments! --- # The Rise of AI Wrappers: Are AI Providers the New Telecoms? URL: https://www.ivanturkovic.com/2025/03/21/the-rise-of-ai-wrappers-are-ai-providers-the-new-telecoms/ Published: 2025-03-21 Topics: AI Artificial intelligence has experienced an explosion of growth in recent years, with companies like OpenAI, Anthropic, and Google leading the charge in providing powerful foundational models. But despite their immense computational capabilities, these AI providers are increasingly finding themselves in a familiar position—one that resembles the fate of telecom giants in the past. In the telecom industry, infrastructure providers built the backbone of communication networks, yet they eventually found themselves competing not on innovation but on price, reliability, and scale. Meanwhile, a new wave of companies emerged, offering user-friendly interfaces and value-added services on top of these networks, becoming the real consumer-facing brands. Today, a similar pattern is emerging in AI, where a new breed of AI wrappers is rapidly capturing market attention and customer loyalty. AI Wrappers: The New Kings of the AI Economy AI wrappers are companies or platforms that use existing AI models but differentiate themselves by offering improved usability, domain-specific expertise, or additional automation capabilities. They act as intermediaries between the raw power of foundational models and the end user, providing a tailored, more accessible experience. These wrappers often build agentic systems that extend AI capabilities beyond simple text generation, integrating AI into real-world workflows more seamlessly. One of the most notable examples is Cursor, an AI-powered coding assistant that integrates deeply with software development environments. While OpenAI provides the underlying models, Cursor enhances them with context awareness, code-specific optimizations, and user-friendly interactions that cater specifically to developers. Similarly, Jasper has become a dominant force in AI-powered content creation, building atop existing LLMs but fine-tuning the experience for marketing and branding purposes. Other examples include: Replit Ghostwriter: An AI-powered coding assistant built into the Replit platform, offering features customized for its user base.Notion AI: A productivity tool that integrates AI to help users generate content, summarize notes, and organize information in an intuitive way.Synthesia: A video generation platform using AI-powered avatars and voice synthesis, making it easier for businesses to create professional content without requiring actors or video production expertise. These AI wrappers add convenience, improve user workflows, and tailor AI capabilities for specific needs—something foundational models alone struggle to do. Why AI Providers Are Like Telecoms Telecom providers built the infrastructure that powered the internet, but their value quickly became commoditized. Consumers didn’t buy "bandwidth"—they bought streaming services, social media, and communication apps that provided the real utility. The underlying networks remained essential but largely invisible. AI is heading down the same path. Foundational model providers like OpenAI, Anthropic, and Google are in an arms race to create more powerful models, but they face two fundamental challenges: Differentiation is difficult: At the model level, once competitors catch up in quality, the main differentiator becomes price and API access rather than unique features.Customer experience is owned by wrappers: Just as customers interact with Netflix rather than their internet provider, most AI users will engage with AI wrappers rather than the underlying models. Unless foundational AI providers can create a breakthrough that redefines the way AI is used, they will likely remain the equivalent of cloud computing providers—essential but invisible to end users. Can AI Providers Compete? AI model creators face a strategic dilemma. Do they continue improving their base models and risk becoming commodities, or do they move up the stack, integrating more directly with users? There are a few potential paths they could take: Vertical integration: OpenAI, for instance, is already moving in this direction with ChatGPT, aiming to make its own application as sticky as possible. Google has integrated Gemini into its search products, while Anthropic is working on making Claude a more accessible assistant.Exclusive partnerships: By offering custom AI models for select partners, providers can maintain an edge. This strategy mirrors what Nvidia has done with its GPU dominance—selling to cloud providers while also enabling high-performance, exclusive partnerships.Fine-tuned solutions: Rather than offering general-purpose models, AI providers could develop industry-specific solutions for enterprise applications, ensuring they remain a step ahead of generic AI wrappers. However, even with these efforts, the fundamental shift in value remains: users prefer tailored experiences over raw power, and AI wrappers are best positioned to deliver those experiences. The Future of AI: Who Wins? The current trend suggests AI providers will continue building the foundation, but the real profits and brand recognition will go to those who package and distribute AI in a way that users love. AI wrappers are already dominating certain verticals, and as agentic AI systems become more advanced, we will see an even greater separation between infrastructure and experience. If AI providers fail to adapt, they risk becoming the "AT&T" of the AI age—powerful but replaceable. Meanwhile, the new kings of AI will be the platforms that understand user needs, integrate AI seamlessly, and create interfaces that feel indispensable. The AI landscape is rapidly shifting, and while foundational models remain crucial, they are no longer the defining factor in AI’s success. The future belongs to those who can bridge the gap between raw AI power and user-friendly experiences. As we move forward, expect more AI wrappers to emerge, reshaping industries and pushing AI providers further into the background. In the end, AI might power the revolution—but the wrappers will own the kingdom. --- # AI: A Gift for Junior Developers, a Curse for Tech Leads URL: https://www.ivanturkovic.com/2025/03/18/ai-a-gift-for-junior-developers-a-curse-for-tech-leads/ Published: 2025-03-18 Topics: AI Artificial intelligence is revolutionizing software development, making it easier for less experienced developers to write code, generate solutions, and build applications faster than ever before. But as AI lowers the barrier to entry, it creates an unexpected challenge—an increasing burden on tech leads who must navigate a landscape filled with AI-assisted code that often lacks structure, scalability, and maintainability. The Rise of AI-Assisted Development Tools like GitHub Copilot, ChatGPT, and other AI-powered coding assistants have significantly boosted developer productivity. Junior and mid-level developers can now produce complex code snippets, automate repetitive tasks, and solve problems they previously struggled with. While this sounds like a win for the industry, the reality is more nuanced. AI-generated code is often syntactically correct but semantically flawed. It may work in isolation but lack architectural integrity when integrated into a broader system. AI lacks the human intuition necessary to understand business logic, future scalability, and team-specific best practices. This means that while development velocity increases, so does the risk of accumulating technical debt. The Burden on Tech Leads Tech leads, already responsible for guiding teams, making architectural decisions, and ensuring high code quality, now face an additional challenge: reviewing and correcting AI-generated code. Here’s why AI can be a curse for tech leads: 1. Increased Review Workload AI accelerates code production, but not always in the right direction. More code means more pull requests, more code reviews, and more debugging. Tech leads spend significant time analyzing whether AI-assisted code is functionally correct and adheres to best practices. For example, a junior developer might use AI to generate an API endpoint for handling user authentication. The AI might produce working code but fail to implement essential security measures such as rate limiting, input validation, or proper session management. The tech lead must then review, refactor, and educate the team on why these aspects are critical. 2. Poor Architectural Decisions Junior and even mid-level developers often rely on AI to solve immediate problems without considering the long-term architectural impact. AI doesn't inherently enforce good design patterns, leading to monolithic structures, poorly optimized queries, or brittle integrations that tech leads must later refactor. Imagine a situation where AI generates multiple SQL queries inside a loop instead of optimizing them into a single batch query. While the code functions correctly, it causes unnecessary database load, which can lead to performance bottlenecks in production. A tech lead must identify and correct such inefficiencies before they escalate. 3. False Confidence in AI Solutions Developers using AI often assume the generated code is correct because it "works." However, AI does not understand the nuances of business requirements, security concerns, or industry-specific regulations. This creates hidden bugs and vulnerabilities that tech leads must catch before they become production issues. For instance, an AI-generated function might handle user passwords but store them in plaintext instead of hashing them properly. If a less-experienced developer deploys this code, it could lead to a serious security breach. A tech lead must not only fix the issue but also implement processes to prevent similar mistakes in the future. 4. AI-Driven Technical Debt Without proper oversight, AI can rapidly generate unmaintainable code that accumulates as technical debt. Tech leads must dedicate additional time to reworking solutions, refactoring poor implementations, and ensuring code consistency across the codebase. Consider a scenario where an AI tool generates dozens of functions across different files with inconsistent naming conventions and redundant logic. While the code technically works, it becomes difficult to navigate, update, or debug. A tech lead must step in to unify the code, remove duplication, and enforce better organizational practices. The Future: More Tech Leads, Not Fewer Paradoxically, as AI tools become more advanced, the need for strong technical leadership increases. More tech leads will be required to: Establish clear coding guidelines and best practices for AI-assisted development.Educate teams on AI’s limitations and how to critically assess its output.Enforce architectural principles to prevent the system from collapsing under its own complexity.Prioritize code quality and long-term maintainability over short-term gains.Introduce automated testing and linting rules to catch common AI-generated mistakes before they reach production. How Tech Leads Can Adapt To thrive in this AI-assisted environment, tech leads need to adjust their approach to leadership and mentorship. Some strategies include: 1. Establish AI Review Processes Tech leads should implement specific review processes for AI-generated code, ensuring that every piece of code passes through a structured evaluation. Code review checklists should include: Security best practices (e.g., input validation, authentication measures).Performance optimizations (e.g., avoiding redundant queries or inefficient loops).Maintainability and readability (e.g., clear variable names, well-structured functions). 2. Promote AI Literacy Among Developers Instead of discouraging AI use, tech leads should train their teams to use AI responsibly. This means educating developers on when to trust AI-generated code, how to refine prompts to get better results, and how to critically evaluate AI suggestions. 3. Use AI to Help, Not Replace, Thoughtful Engineering Tech leads can leverage AI themselves—not just to generate code, but to automate tedious tasks like refactoring, documentation generation, and code linting. By integrating AI into CI/CD pipelines, tech leads can ensure AI-generated code is continuously checked for quality issues before deployment. AI is undoubtedly a powerful tool for accelerating development, but it is not a substitute for experience, intuition, and thoughtful engineering. While junior developers benefit from AI assistance, tech leads bear the responsibility of ensuring that AI-generated code aligns with best practices and long-term business objectives. As AI continues to shape the industry, the role of tech leads will become even more critical—not just in fixing what has been done, but in guiding teams to use AI responsibly and effectively. In the end, AI doesn’t replace the need for great engineering—it amplifies the need for strong technical leadership. The companies that recognize this will be the ones that successfully integrate AI into their development processes without sacrificing code quality, security, or scalability. --- # Decisions, decisions, decisions URL: https://www.ivanturkovic.com/2021/01/03/decisions-decisions-decisions/ Published: 2021-01-03 Topics: Software Engineering Year 2020 has taught us many things but not everyone was listening to the lectures. We are living in constantly evolving world and no matter how much we want to standardize our lives and how much we hate changes they happen all the time. Last year really showed us we cannot control the future and not even shape it based on our needs. This year should be different they say, there is a lot of hope but are there a lot of people who are willing to adjust to changes constantly. We love our comfortables lives and we don't want to take step back even if this would take us two or more steps forward in the future. I understand change is hard so hard we sometimes manage to distinguish between common sense and what we want to believe. Internet has evolved and managed to bring those distortion further than ever. I've decided to start writing about life, then changes that are ever lasting, the planning that make solid plans for the future, how to test our plans and how to get out and reach as many people possible with our plan. Whether this is a personal self improvement or a business idea. I have entered into third decades of working with technology, turning ideas into reality, making those ideas available to broad public, see what works and what it doesn't. I did learn a lot but I did a lot of mistakes as well. I think this is the decade I want to share my knowledge and experience with others so they can save some time on their own path. Realizing new ideas has nothing to do with earning a lot of money or becoming famous, they are just a byproduct of a successfully launched and realized ideas. The ideas that solve people's problem achieve success and it just depends on you how far it takes you to understand if your idea does really solve any problem at all. --- # Homebrew: How to start and stop background services URL: https://www.ivanturkovic.com/2015/04/10/homebrew-start-stop-services/ Published: 2015-04-10 Topics: Software Engineering View image | gettyimages.com Anyone that has installed application running in the background from Homebrew, knows how to use launchctl that actually runs every application when the computer restarts. It is pretty straight forward task but most of the time you need to know the location of the .plist file that defines how to run it installed with homebrew. Now there is a quicker path how to control them by simply adding following package to home-brew: brew tap gapple/services  And you have available to start, stop services directly from home-brew with following command to get a list of active services: brew services list So for example if you have installed postgresql you can start it as a background service with: brew services start postgresql or stop the service with brew services stop postgresql Which is much more easier and simpler than finding the right .plist file inside cellar folder of brew --- # Stop procrastinating! How to prevent it. URL: https://www.ivanturkovic.com/2014/11/27/stop-procrastinating-and-be-productive/ Published: 2014-11-27 Topics: Career & Leadership, Startups & Business Still trying to stop procrastinating? There are probably numerous days that you site behind the computer to do some research or get some work done and doing a short break to read some news or check social updates and as you done this you aren't aware that time passes as you jump from one link to another while the time is passing by rapidly. You have probably tried many things, like avoiding to use those sites, setting time aside for short breaks or some other solution but every time you spend more time doing nothing than to spend that time into something productive. My way of curbing procrastination time to minimum You probably procrastinate as everyone but you don't do it so efficiently, there are many ways to curb this behavior especially with avoiding reading the news updates all the time. We live in an era of information overload and we developed a habit of a need to be constantly updated with latest updates. I am using few tools which are completely free of cost for basic usage you will need to prevent procrastination. Here is how I do it: I have installed self control app on my computer and you can get it at this link. It is free of cost. Basically what it does is that you add a list of web sites that you want to block for a selected period of time. You are probably wondering now which pages should I add to this blacklist. Well the obvious sites should be the social networks. Most of the links we click to other sites are coming from there to funny videos or interesting stories. There are ton of these sites, but major one should be Facebook, Twitter, Google+ etc. Though have in mind if you develop for social network logins you have to keep alert to remove them for that period otherwise you can do no related work to it for that day. Other useful list of sites that need to be blacklisted are on your history list in your favorite browser. Go through the history and check all the sites that shouldn't be there during your work hours and add them to the list. Next step is to add time inside self control for how long it should be blocked. First advice never do mistake and set it over 24 hours. Perfect time to set the limits is 10-12 hours. You would probably say I don't work so many hours and I agree you mustn't but from the time you set the time and all other chores you need to do during the day believe me that is the most optimal time especially if you are working for yourself or being in a startup environment. It is dynamic over the day so keep on tracking your time that way. So next step is to activate the self control and you magically stop procrastinating. Wrong. Keep on reading.   This step is just a prevention measure before building a habit of being productive and stop procrastinating once for all. :) Obviously you still need a way to get some fix on news updates and you can satisfy this craving by reading it on mobile or tablet. Taking breaks from work and reading it on different screen than you do your work helps a lot to give you a feeling that you are wasting time and don't finish work especially if you are in an office full of people their looks give you silent reminder to get going. So to get rid of the procrastination and urge for news I use a service called Feedly, all the sites I used to read I save them now into Feedly. It is a convenient rss reader than manages and updates all the news for you and have a great native mobile app to share and save content for later. Best thing I like is the information of average number of posts per day. There are two kind of sites that you should add to Feedly and love it reading: Sites you should add are those which are relevant you and they have a low average number of posts per day, since feedly gives you ability to organize them into categories. So use sites from this kind in different category than from the second one. The reason follows in the next point. Sites that are somehow relevant to you and general sites that you follow on daily basis and have a high average post rate. Why should you separate those two categories is fairly simple. Thinking that you will be able to consume every information that comes in and have focus to go over it every day I can assure you it is impossible. So what to do with categories that stockpile a backlog of posts which you never will be able read. It is simple do fresh start for that category. Don't worry you didn't miss much anyways ;) This is the reason why relevant should be separated from this one so you can still follow important low post frequency than this high frequency sites. How do I usually read this updates on my phone? Well I do this while commuting or taking a short break while there is nobody near to have a chat. Basically anywhere you are waiting for something to happen but you cannot affect personally. There is one more app that comes to help. During the day I usually skim just the title and first paragraph. In the past I would do the following think how interesting is this article and how I will use it in the future I would just make a bookmark inside the browser but I would never read it. I did get rid of that clutter a while ago. I had more than 7000 bookmark links. For that part I use Pocket service which enables you to save your favorite links, photos and other media for later reading. I dislike any service integrated directly in the browser and prefer having separate application for that use. I like Pocket because it saves all the articles for offline reading so you can basically read it in offline mode or while traveling by air. As I said before I usually skim the articles and if I think that some is worth reading for future I save it into Pocket, Feedly app works well so with a single button I can save it into Pocket. This way I read full article when I am fully focused or have time to read it through. Pocket gives you ability to archive the articles which you think they would be useful in the future and also relevant to your future work. How much information is too much to stop procrastinating? Well if on average through the day inside Feedly app you see total unread more than 200 posts per day you are already doing it wrong. Try to limit the number of sites or use a specific rss for the same site, each section of the site usually has more specific feed to try to narrow it down and avoid general feed from high frequency sites. If you manage to keep it under 200 posts per day, fast skimming through that will not take you more than 15 minutes per day. So not just you will stop procrastinating but also free your time for other things you can enjoy in your free time. What about social sites? This is another thing. I would advise you to install native applications that give you notifications and best and most streamlined experience on the mobile phone, but before you are doing it I advise you to unfollow any high frequency brand or a person that is all over your timeline. This can easily happen on Twitter where you get fairly lost and hard to follow any updates. Same for the Facebook. You can still be a friend with someone but use the unfollow button on their profile and you won't get their posts on timeline, same apply for specific sites that virally spam your timeline. Another part of social sites and communication are probably messenger (IM) applications that you probably loose too much time than you planned and as you finish with one chat another one comes up. I use simple thing, you should set a timer alert to specific time, for example 15 minutes. If you are chatting with someone while you shuld finish something and do it more than a quarter of hour then you are wasting time. Everything can wait for few hours especially random chat messages. By setting the timer alert the strong alert noise will make a hard wake up call and a distraction to fall out of the conversation. Final most important thing you need to do While working you should turn off audio notifications from all sources to minimize distractions that includes vibration too. If you have your phone on the table instead in your pocket make sure the screen is facing downwards and this way preventing any visual distraction from your work. Final words If you manage to set up this habit on a daily level and continue doing it for at least 2-3 weeks you will notice that slowly you are evolving and your need to turn on self control every day is redundant since you've created a new habit that makes you stop procrastinating and more productive. I hope you will get over procrastination phase as it helped me to become a more productive person and have a more quality time off besides my work. Related articles   {beat procrastination}The 5 Minute Timer and a super duper list of handy links Planning Can Help You Conquer Procrastination 30 Ways to Productively Procrastinate Positive Procrastination We Can Do This, Cap! The Art of Procrastination - a Film   --- # AngularJS ngInclude directive and scope inheritance URL: https://www.ivanturkovic.com/2014/11/26/angularjs-nginclude-directive-scope/ Published: 2014-11-26 Topics: JavaScript & Frontend, Software Engineering ngInclude directive and scope There are many times when you want to include a html snippet code from another file but preserve the scope of it. It is usually when you have different form fields for the various objects and you want to have a global controller that oversees the updating of different forms. So if you want to take the quickest route and use ngInclude directive you would be surprised that it is not properly linking to your controller and you cannot access the form instance. This is due to ngInclude internals and how they work. ngInclude creates for each use as a new child scope so overwriting anything inside the new included HTML file content will be written into child scope and not in the one you've anticipated to be. So there are few workaround around this as creating a new object inside the scope for example $scope.data = {} inside the controlling controller and then in the imported html file set values inside the This works if you don't have a problem with static value being inserted into all html files, but if you want maximum flexibility then this is not the perfect solution. So after inspecting the source code inside ngInclude.js, I have seen a room for improvement and created a similar directive to ngInclude called ngInsert, which instead of making new child scope it inherits the current scope and continue using it inside. You can pick up the whole source code at this gist. You can use it in the same manner as existing ngInclude. I am also appending here the full source code for further reading. (function(){ 'use strict'; angular .module('app', []) .directive('ngInsert', ngInsert) .directive('ngInsert', ngInsertFillContentDirective); ngInsert = ['$templateRequest', '$anchorScroll', '$animate', '$sce']; function ngInsert($templateRequest, $anchorScroll, $animate, $sce) { return { restrict: 'ECA', priority: 400, terminal: true, transclude: 'element', controller: angular.noop, compile: function(element, attr) { var srcExp = attr.ngInsert || attr.src, onloadExp = attr.onload || '', preserveScope = attr.preserveScope || true, autoScrollExp = attr.autoscroll; return function(scope, $element, $attr, ctrl, $transclude) { var changeCounter = 0, currentScope, previousElement, currentElement; var cleanupLastInsertContent = function() { if (previousElement) { previousElement.remove(); previousElement = null; } if (currentScope) { currentScope.$destroy(); currentScope = null; } if (currentElement) { $animate.leave(currentElement).then(function() { previousElement = null; }); previousElement = currentElement; currentElement = null; } }; scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { var afterAnimation = function() { if (angular.isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { $anchorScroll(); } }; var thisChangeId = ++changeCounter; if (src) { $templateRequest(src, true).then(function(response) { if (thisChangeId !== changeCounter) return; var newScope = scope.$parent; if (!preserveScope) newScope = scope.$new(); ctrl.template = response; var clone = $transclude(newScope, function(clone) { cleanupLastInsertContent(); $animate.enter(clone, null, $element).then(afterAnimation); }); currentScope = newScope; currentElement = clone; currentScope.$emit('$insertContentLoaded', src); scope.$eval(onloadExp); }, function() { if (thisChangeId === changeCounter) { cleanupLastInsertContent(); scope.$emit('$insertContentError', src); } }); scope.$emit('$insertContentRequested', src); } else { cleanupLastInsertContent(); ctrl.template = null; } }); }; } }; } ngInsertFillContentDirective = ['$compile']; function ngInsertFillContentDirective($compile) { return { restrict: 'ECA', priority: -400, require: 'ngInsert', link: function(scope, $element, $attr, ctrl) { if (/SVG/.test($element[0].toString())) { // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not // support innerHTML, so detect this here and try to generate the contents // specially. $element.empty(); $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope, function namespaceAdaptedClone(clone) { $element.append(clone); }, {futureParentElement: $element}); return; } $element.html(ctrl.template); $compile($element.contents())(scope); } }; } })(); --- # Extending Javascript objects with a help of AngularJS extend method URL: https://www.ivanturkovic.com/2014/11/22/extending-javascript-objects-help-angularjs-extend-method/ Published: 2014-11-22 Topics: JavaScript & Frontend Multiple ways of extending javascript objects /** * One-way reference from subclass to superclass (instance) * Most of the time this is what you want. It should be done * before adding other methods to Subclass. */ ChildClass.prototype = new SuperClass(); /** * Two-way reference * Superclass will also get any Subclass methods added later. */ ChildClass.prototype = SuperClass.prototype; /** * Cloning behavior * This does not setup a reference, so instanceof will not work. */ angular.extend(ChildClass.prototype, SuperClass.prototype); /** * Enhancing a single instance * This could be used to implement the decorator pattern. */ angular.extend(subClassInstance, SuperClass.prototype); --- # Be that likable person URL: https://www.ivanturkovic.com/2014/11/21/likable-person/ Published: 2014-11-21 Topics: Career & Leadership Become genuinely interested in other people. Smile. Remember that a person's name is to that person the sweetest and most important sound in any language. Be a good listener. Encourage others to talk about themselves. Talk in terms of the other person's interests. Make the other person feel important and do it sincerely. Smile, smile, smile :) --- # AngularJS directive multiple element URL: https://www.ivanturkovic.com/2014/11/21/angularjs-directive-multiple-element/ Published: 2014-11-21 Topics: JavaScript & Frontend How to get AngularJS directive multiple element You probably know there exists opening ng-repeat-start and finishing directive ng-repeat-end like:
  • {{user.firstName}}
  • {{user.firstName}}
You would think it is a specially made directive that works that way but actually it is not. It is part of any directive so you can use it inside your too. When you create new directory and inside you factory function you need to return an object with multiElement options set to true. This way it will behave same as ngRepeat directive over multiple non nested fields. Small excerpt from ngRepeat.js file denoting this: var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { ... return { restrict: 'A', multiElement: true, transclude: 'element', priority: 1000, terminal: true, ... } }]; From native angularJS directive there are few more directives that support multi element spanning. They are ngIf, ngShow, ngHide and ngSwitch. So by adding multiElement to your custom directive you can make to transclude over angularjs directive multiple elements at the same time. This also comes handy because you can do nesting of multiple elements if they are contained inside another one. --- # How to record iPhone screen URL: https://www.ivanturkovic.com/2014/11/15/record-iphone-screen/ Published: 2014-11-15 Topics: Software Engineering, Tools To record iPhone screen has never been easier Developing great new iOS application requires not just a usable application, but also a great video presentation of it. How it works, basic usability tricks to make users want to get your new application which you spent numerous hours to make it perfect. If you are working alone on next big thing or inside a small agency or team and you probably have a limited budget to outsource your video production one of the hardest parts was shooting the actual content from iOS device, whether it is iPhone or iPad. Until now you could use some kind of recording of iOS simulator or using airplay method to sync the video. It worked but it was somehow clunky experience and you needed to buy extra software. With the release of Mac OS Yosemite version everything has changed. Now you can record you iOS device screen directly without buying additional software whatsoever. All you need to have the computer upgraded to Yosemite version and an iPhone or iPad to latest iOS 8. iOS device needs to connect to your computer through lighting cable. Instructions Here are the steps you need to do: Launch Quicktime Player Select File > New Movie Recording (you can use the keyboard shortcut Option-Command-N) As you can see this launches new window and by default it activates your built-in iSight camera, but before we go to next step make sure your iOS device is plugged into the computer so it will be visible in the next step From the screenshot you can see that switching to record iOS device requires to select the video input from input source dropdown menu near the record button. As you can see I have selected my iPhone as source and immediately you should see the screen mirroring on your computer Even though you are getting the live view of the iOS device screen the preview is not always perfecrtly synched on the window you are seeing, but don't worry this is just on the window, the video that is being recorded will come out great without any delays you see it on the screen, thus it is perfect to use for presentation videos but it is not meant to be used for mirroring screen for other uses such as playing games on your computer form iPhone or using it to present the screen for larger audience, this is still what I recommend to use some kind of airplay transmission. Here you have it. To record iPhone screen has never been easier with latest software update. --- # Be a better person URL: https://www.ivanturkovic.com/2014/11/08/better-person/ Published: 2014-11-08 Topics: Career & Leadership Don't criticize, condemn or complain Give honest and sincere appreciation Arouse in the other person an eager want --- # AngularJS and Ruby on Rails work together URL: https://www.ivanturkovic.com/2014/10/24/ruby-rails-angularjs-together/ Published: 2014-10-24 Topics: JavaScript & Frontend, Ruby on Rails, Software Engineering Finding the best integration of AngularJS and Ruby on Rails Recently I got really excited with AngularJS so to make it work perfectly with Ruby on Rails there are some configurations needed. There are available blog posts on how to integrate it perfectly but somehow I wasn't happy with the available preferences. Some offered to add the javascript files manually to the project and to manual project organization, others offered some kind of gem packager or even automated as a rails app template. My goal is to describe how to start new rails app project from the scratch but the instructions should be succinct enough to be able to reuse them for the existing project (Actually I did extract it from the existing application I am working on). For the front end development I recently discovered great gem that really can make it more closer to pure full stack javascript development. Bower is a great javascript package manager by the people who are working on Twitter Boostrap. It is like Bundler but made for javascript instead of ruby language. When I found out that there is a ruby gem that is integrated with rake tasks so I can easily update all javascript libraries without needing adding gem library for each one. The gem is called bower-rails. My initial plan is to evolve this post into a series of blog posts on how to develop a fully functional demo application so I've included some steps that are might not needed but are good to have. Don't worry I will provide explanation why I am using each of them. Here is my plan what I will try to achieve with this series of posts: creating a new demo project with angularJS from scratch, showing all my changes along the way, and trying to explain every step. this will include creating basic rails 4 app adding basic gems setup front end development with Bower adding angularJS implementing basic Rails and AngularJS controllers Prerequisites In this post we will use some of the predefined versions of  libraries to make it work. Mac OS Yosemite operating system, though it shouldn't be much different for *nix environment. Ruby 2.1.3 Ruby on Rails 4.1.6 AngularJS 1.3.0 Postgresql Nodejs Bower Haml for writing views Sass for styling views Your favorite code editor. I am using Sublime text editor but any you feel comfortable is good enough Initializing the new Rails 4.1.6 app Probably you know this step already. Everyone uses them to start new rails project. We will call this application todo and use few flags to change default settings for the test framework and use Postgresql database. rails new todo --skip-test-unit --skip-bundle --quiet --database=postgresql We are intentionally skipping building a bundle for now until we add additional gems to the Gemfile, configure the database and make sure we have a clean slate base on which we are going to work on. HINT: We all are sometimes lazy so it is good to put all these arguments into ~/.railsrc file so every time you run new generator it will reuse them without needing to write them all: -B #Skip Bundle -T #Skip Test-Unit -d postgresql #Use postgres now let's continue... cd todo General gems that are good to have There are usually a handful of gems that I usually add to my new project and I will go little bit in details. First of all we will need to remove turbolinks gem from the Gemfile since it doesn't fit the usage inside the single page application. Here are the following gems we will add: Common gems that are related to make develop with ease. So let's add following lines to Gemfile # use haml for generators as default templating gem 'haml-rails' # better form building gem 'simple-form' # list of countries for country dropdown gem 'country_select' # nested forms gem 'nested_forms' # great addition to sass/scss functionality gem 'compass' # advanced error page for development environment, with ruby shell gem 'better_errors' gem 'binding_of_caller' # don't show asset requests in development log file gem 'quiet_assets' # making your controllers inherit all restful actions gem 'responders' # powerful alternative to the IRB gem 'pry' We have disabled default testing framework since I usually use RSpec and Capybara with Selenium for browser based testing of features. group :test, :development do gem 'rspec' gem 'rspec-rails', '~> 2.0' gem 'factory_girl_rails', '~> 4.0' gem 'capybara' gem 'database_cleaner' gem 'selenium-webdriver' end To make it work with AngularJS I will also add two gems that are meant to support javascript libraries. gem 'bower-rails' gem 'angular-rails-templates' I've already described the bower-rails gem that giver you the power of Bower inside rails project. Angular-rails-templates gem is another one that makes rails working with angular templates much easier. It creates a cache of templates that doesn't require AJAX requests for each template. If some gems are not descriptive enough please do google them and read all the functionality they offer. So here is the full Gemfile file content. source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '4.1.6' # Use postgresql as the database for Active Record gem 'pg' # Use SCSS for stylesheets gem 'sass-rails', '~> 4.0.3' # great addition to sass/scss functionality gem 'compass' # Use Uglifier as compressor for JavaScript assets gem 'uglifier', '>= 1.3.0' # Use CoffeeScript for .js.coffee assets and views gem 'coffee-rails', '~> 4.0.0' # See https://github.com/sstephenson/execjs#readme for more supported runtimes # gem 'therubyracer', platforms: :ruby # Use jquery as the JavaScript library gem 'jquery-rails' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', '~> 0.4.0', group: :doc # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring', group: :development group :development do gem 'quiet_assets' gem 'better_errors' gem 'binding_of_caller' gem 'meta_request' gem 'letter_opener' gem 'rb-fsevent', require: false gem 'terminal-notifier-guard' end group :test, :development do gem 'rspec-rails', '~> 2.0' gem 'shoulda-matchers' gem 'factory_girl_rails' gem 'hirb' end group :test do gem 'email_spec' gem 'cucumber-rails', require: false gem 'guard-cucumber' gem 'database_cleaner' gem 'launchy' gem 'capybara' gem 'capybara-webkit' gem 'headless' gem 'faker' gem 'forgery' gem 'rack-test' end # AngularJS support gem 'bower-rails' gem 'angular-rails-templates' # use haml for generators as default templating gem 'haml-rails' # better form building gem 'simple_form' # list of countries for country dropdown gem 'country_select' # nested forms gem 'nested_form' # advanced error page for development environment, with ruby shell # making your controllers inherit all restful actions gem 'responders' # powerful alternative to the IRB gem 'pry' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' # Use unicorn as the app server # gem 'unicorn' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development # Use debugger gem 'byebug', group: [:development, :test] One things I dislike being generated When you generate new project by default generator for models, controllers etc. usually generate a bunch of unnecessary files like helpers, stylesheets, javascript files that are dependent on the domain so to disable them we add the following code to the ./config/application.rb: config.generators.stylesheets = false config.generators.javascripts = false config.generators.helper = false Configure database.yml and example database file Since Ruby on Rails 4.1 you have new file called secrets.yml that can be used for adding private informations like database access information. So let's modify it to be able to deploy project to repository without exposing private information. First we add to ./.gitignore file exception not to include secrets.yml file config/secrets.yml And make an example copy file for secrets template cp config/secrets.yml config/secrets.yml.example Now you can add details to secrets for username and password for database connection. Rewrite database.yml file to look like: default: &default host: <%= Rails.application.secrets[:database][:host] %> adapter: postgresql encoding: UTF8 database: <%= Rails.application.secrets[:database][:name] %> pool: 10 reaping_frequency: 30 username: <%= Rails.application.secrets[:database][:username] %> password: <%= Rails.application.secrets[:database][:password] %> development: *default test: *default production: *default And now inside secrets.yml.example file add the missing fields like: development: secret_key_base: 2f040beb7605141a4b10a68a1121ea476cc5e86bd837bff2b9925144f013ece49c71816be0d24c721d8f9aa70519cb5c80abe3b0ffb01e12a13e6d8dc59400ab database: :host: localhost :name: todo_development :username: root :password: password test: secret_key_base: 9bb840e19afc8215aa989ee9a98e5a176f6006ce5c1fcbdc322847b6d2b57d826031996d6115675917142f3cb66186501f858aa6c0b7c308024343a07409cc71 database: :host: localhost :name: todo_test :username: root :password: password # Do not keep production secrets in the repository, # instead read values from the environment. production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> database: :host: localhost :name: todo_production :username: root :password: password Bower and front end dependency management There is not any provided way from rails directly to manage front-end libraries and extensions that are not deployed as a gem. Tricking them and packaging them into gem as they would represent Rails Engines which are really not. We are going to use Bower to manage front-end resources for javascript supported front end development. Here is the bower-rails gem that will merge the usefulness of only javascript full stack development and the development with rails. Prerequisite for this gem is that we have already installed npm (node package manager) that it is part of the node. Besides node library we also need to install bower library. Easiest way to install it is through homebrew, here is the following command to install them both brew install node && npm install bower For more details please read documentation for homebrew and npm Let's bundle the gems Run bundle install to install all the required ruby gems for our project. Now when you see that all are successfully built let's add all the front-end library dependencies with Bower. To install dependencies to directories let's run the initializer to generate custom bower.json rails g bower_rails:initialize json We can also run rails g bower_rails:initialize that will generate Bowerfile file that has similar syntax as Bundler's Gemfile but for this tutorial I will rather use the same syntax with json file that is being used in javascript development. Let's add angularjs and angular route dependency for javascript to ./bower.json { "lib": { "name": "bower-rails generated lib assets", "dependencies": { "angular": "latest", "angular-route": "latest" } }, "vendor": { "name": "bower-rails generated vendor assets", "dependencies": { } } } and run following command to install them rake bower:install Now when we have installed resources we can add the required javascript files to application.js file which should look like this. //= require jquery //= require jquery_ujs //= require angular //= require angular-route //= require angular-rails-templates //= require_tree . Let us make a folder where we will hold all the templates mkdir app/assets/templates/ Now before trying out if everything works as needed there are few more steps to finish. First we need to remove all turbolinks references in application layout file app/views/layouts/application.html.erb:  <%= stylesheet_link_tag 'application', media: 'all' %> <%= javascript_include_tag 'application' %> <%= csrf_meta_tags %> Next step to do is to create database for the project rake db:create We can try to run server for the first time and check if everything is running okay. rails server If we visit http://localhost:3000/ in your favorite web browser you should see the following screen: YES So we did it :) We have everything working as it should be with no errors. If you get any problems don't hesitate to comment below. First AngularJS page We don't stop just adding all the resources without writing at least a small angularjs page to show that it works as it should be working. Add new route to ./config/routes.rb file: Rails.application.routes.draw do root 'home#index' end And a accompanying controller with generator rails g controller Home index class HomeController < ApplicationController def index end end We need to add barebones AngularJS app so we will put this into app/assets/javascripts/app.js file var todo = angular.module('todoApp', []); Last thing we need to do is to add some content to the index.html.haml page .container-fluid{ 'ng-app' => 'todoApp'} .panel.panel-success .panel-heading %h1{ 'ng-if' => 'name' } {{name}} .panel-body %form.form-inline .form-group %input.form-control{ type: 'text', placeholder: 'Enter your name', autofocus: 'autofocus', 'ng-model' => 'name'} Now if you go back to the browser and refresh the page you should see the text input field. Try to write something in it should show an updated text above the field. So here it is. We have not just added few simple javascript files to the rails pipeline but prepared a full project for front-end and back-end development where we can easy introduce the whole projects that we've been playing with javascript only. Final version of the demo project should be similar to the following code on the github --- # Timesaving AngularJS Cheatsheet URL: https://www.ivanturkovic.com/2014/10/23/timesaving-angularjs-cheatsheet/ Published: 2014-10-23 Topics: JavaScript & Frontend Perfect way to get start with AngularJS There are two great things to use for looking up for AngularJS syntax, Dash.app with downloaded AngularJS documentation and a short cheatsheet by ProLoser. It helps you to speed up retrieval and common questions.   --- # Four Questions About Leadership URL: https://www.ivanturkovic.com/2014/06/16/four-questions-about-leadership/ Published: 2014-06-16 Topics: Career & Leadership, Startups & Business I hear four questions asked about leadership often. This article gives a short answer to each of these important questions. Why Does Leadership Matter? Parents universally hope that their children develop leadership qualities. They know that leaders are people who are effective in what they do, are respected by others, and typically rewarded for those skills in a variety of ways. It is in these formative years that, through our parents, we first see leadership as desirable and important. As young people we look up to people around us that motivate and listen to us people that seem like real-life heroes. We consider these people leaders. As we grow we begin to relate leaders to their jobs and ministers, teachers, police officers. And later Mayors, Presidents, and CEO's... As adults all of these thoughts and experiences define why we think leaders have desirable traits and play roles we admire (and why we desire these things for our children). All of these experiences and thoughts help us define why leadership matters and it matters because leaders make a difference and can shape the future. It matters because leaders are valued and valuable. In everyone's mind leadership, especially when it is good, matters. What is a Leader? A leader is a person who sees something that needs to be done, knows that they can help make it happen, and gets started. A leader sees opportunity and captures it. A leader sees a future that can be different and better, and helps others see that picture too. A leader knows they canít do it alone. A leader is a coach. A leader is an encourager. A leader views change as their ally. A leader is willing to take risks today for something better tomorrow. A leader is a learner. A leader is a communicator. A leader is a coordinator. A leader is a listener. A leader takes a long view and letting their vision keep their daily steps on track. A leader is passionate. A leader motivates and inspires. A leader values results. A leader cares about more than results though, it cares about those who are following her lead. A leader makes a difference in the lives of others. A leader is all of these things and much more. Are People Born Leaders? Sure they are ñ I mean everyone is born, right? You might say that riddle-like answer misses the point. You say the real answer is that some people are truly born to lead. And I would reply that your common statement implies that others arenít born to be leaders. So letís examine that difference of opinion... When people describe someone as a ìborn leaderî they typically mean that the person is motivating, a good communicator and charismatic. And it is true; some people are blessed at birth with more natural ability in these ways. But leaders can be great with different innate characteristics as well. And there is no single small skill set that defines the perfect leader or guarantees success. Everyone is born with a unique set of natural abilities. And all of us can develop skills and styles to complement those natural abilities. Who is a Leader? This question on the surface is the easiest question Iíve asked so far. After all, Iíve already given some examples. People in certain roles are leaders, whether theyíve studied for the role, like a doctor, lawyer, teacher or ministerÖ got elected to the role, like a county councilman, mayor, Senator or President... or worked up the through the organization like a supervisor, manager, Vice President or CEO. You can ask most anyone the question ìWho is a leader?î and those are the kinds of answers they will give you. They are right, of course. But they are only partially right. Leaders arenít leaders because of a job title. Leaders are leaders because they lead. Which takes me back to my previous question ñ ìAre people born leaders?î Yes they are. But it isnít just a few that have been hand picked by our Creator or random genetics. We have all been picked ñ genetics has selected us all. We were all born to lead, in our own way. We may not be the Chairman of the Board. We may not be the person on the stage. We may not lead with oratory or flair. We may lead by compassion. We may lead by example. We all can lead. We all have the ability to be remarkable leaders. Leadership isn't about position. Leadership isn't about power. Leadership is about potential and your potential. You are a leader. Claim and believe this to be true, for it is. Stake your claim and make a difference in the world around you. Your opportunities for leadership are endless. The rewards are boundless. My answers to four questions lead to a question for you. Where will you lead? --- # Ambitions URL: https://www.ivanturkovic.com/2013/04/05/ambitions/ Published: 2013-04-05 Topics: Career & Leadership I aim to become the president of my country. I have the object of becoming the greatest scientist. I will one day be the richest person in the world. I aim to and so goes the list of ambitions of people. Nobody remembers that the final goal for each one of us is going back to earth. Let us talk more about ambitions. Are ambitions good or bad? This is difficult to answer, isn't it? To be an ambitious person must be very good. With ambitions a person strives to achieve a great feat. Few of us have very big ambitions and few very small. It all depends on our contentment. What will make us feel happy? What will make us feel worthy, and what will make us feel powerful? It's all about what we want from life. If you want money at any cost, you will compromise with all other values and try to make more money. But if your ambition is to become an honest person and live a God fearing life, no amount of money can attract you to do any wrongs. Ambitions therefore depend upon our character, culture, mental makeup, outlook, values and so on. Till our ambitions don't trouble others it is ok. If by achieving our goals, we make others happy, that is still better. But if our ambitions hurt others, we must revisit our thinking and redefine our ambitions. --- # How to be success URL: https://www.ivanturkovic.com/2013/04/02/how-to-be-successful/ Published: 2013-04-02 Topics: Career & Leadership You may be smart, you may be talented, you may be handsome, but you aren't successful. After all, the guy that never was that gifted or never had great ideas and he has less brains than you, is now your supervisor! How did that happen? Why do all of us with the knack to be great, never saw our dreams come true? And what is the common characteristic between successful people? According to statistical researches, what divides successful people from non-successful is that the former had a particular goal in their life and worked hard to achieve it. On the contrary, people that had many goals and ideas, but spent more time talking about them than working on them, never managed to make their dreams come true. So, the first thing you have to do in order to succeed is to find a goal. How do you imagine yourself in five or ten years from now? Would you like to have your present job? Would you like to be sitting in a small office, working many hours for little money? Or do you see yourself on a quiet beach, on a vacation with your family? If the second option seems better to you, maybe you need a better paid job. Think of all the alternatives. For instance, is it better for you to keep your present job and try to get promoted, or should you apply for another job? Or maybe you should think of starting your own business? Your goal must be specific. If your goal is not clear, you will never manage to achieve it. When you have your specific goal, write it down. It is always helpful when you see your thoughts in writing. If your goal seems too difficult to achieve, divide it into smaller goals. For example, divide your goal into four smaller ones, where each of them will consist of a small step which will bring you closer to your initial goal. Moreover, it's always good to have people that support you around. But you canít expect our friends or family to push you to make your dreams come true. The only one responsible for your success or failure is you. Do not be afraid to follow your steps towards to your goal. Nothing is too difficult. After all, everything starts from a dream; and remember that you can achieve anything if you just believe in it. --- # Why Setting Personal Goals Is Important URL: https://www.ivanturkovic.com/2013/03/28/why-setting-personal-goals-is-important/ Published: 2013-03-28 Topics: Career & Leadership A lot of people like to learn how to be successful. They read books, listen to audio cassettes or even watch videos that tell them how to be successful in life. However most people fail to apply that knowledge, and as a result a lot of what they learn is ultimately wasted. I know I am guilty of this myself. I once learnt about the importance of setting personal goals, but never applied that knowledge. For some reason I studied it, became interested in it, but took it no further. But later I then heard an expression that changed the way I looked at and ultimately used information I acquired. That expression was, ìwhat's the pointing of learning something, if you never use itî. As soon as I heard that saying, I realized that I had been learning lots of things, but never applying them in my life. Learning how to set goals, was one of those things. So Why Is Goal Setting Important Anyway? Taking the time to sit down and write out your personal goals is perhaps one of the most important things you can do for yourself. And incidentally, all successful people set themselves goals. That in itself should be a big enough reason to set them! What Are Goals? Goals are simply the things you want to do in life, and by writing them down you can clarify exactly what you want and how you are going to accomplish them. Most people have a vague idea of what they want to achieve in life, and keep these vague ideas in their head. However, because they are vague ideas the action they take to accomplish those goals is also vague. And as a result, usually ineffective. Personal Goals Will Focus The Mind So writing down goals on a piece of paper allows you to decide exactly what you want to do in life. Once you know what you want to do, you can then create a specific plan of action to accomplish those goals. It's like giving yourself a map and compass to reach a certain destination in life. Without that map and compass you would most likely loose your way, and never get to where you want to go. This is the real value of goals, as they help to give your life meaning, direction and a sense of purpose. With these things will also come a sense of control, which will also make you feel happier with yourself. You will be steering your life in the direction you want it to go, rather than being blown around like a leaf in the wind. Set Yourself Some Goals! So take some time to think about the things you want to accomplish in life. Write them down on a piece of paper, and create a plan as to how you will achieve those goals. It might just be the most important and valuable exercise you ever do.   --- # Breaking Bad Habits Of Failure URL: https://www.ivanturkovic.com/2013/03/26/breaking-bad-habits-of-failure/ Published: 2013-03-26 Topics: Career & Leadership People living today are extremely lucky. 50 years ago the internet never even existed, and cable TV was something you read about in sci-fi magazines. Now, thanks to the internet, you can find information on virtually anything you want, instantly. Something which was only a dream just 10 years ago. Yet with all this information available to us, people still fail to become successful in life. Why is this? After all, there is so much self help information available telling us how to be successful, but most people still fail to achieve success. And why do people still suffer from depression, when there are so many books telling them how to live a great life? The fact is, information is not the problem. And no matter how much technology advances, people will always have the same problems. What Are The Reasons People Fail To Achieve Success? If you ask someone why they never succeeded in what they were trying to do, they will most likely tell you it was someone elseís fault, or something happened that was beyond their control. However if you compare that persons life against someone else's life, you could probably find many examples of people who suffered under far worse conditions yet still became a success. This shows us that the real reason people fail is not because of something outside of them, rather the reason lies within. Internal Reasons For Failure When you continue to do the same thing over and over again it is called a habit. You are probably familiar with bad habits such as smoking, but are you familiar with the habits of failure or the habits of success?   Creating Habits Of Success Since success or failure is ultimately achieved through the actions you take, habits therefore play a tremendous role in determining whether you will achieve success or failure in life. For example. If you read everyday, that is a habit. This habit is likely to expand your knowledge of a subject, and vastly improve the chances you will master and be successful at it. However, let's say that instead of reading everyday, you prefer to sit down and watch TV for a few hours. Do you think this will help you master any subject? Or become an expert in your field? In all likelihood, the answer will be no. So if you look at achieving success or failure in terms of the actions you repeat on a daily basis, then it is quite simple to understand why some people succeed, and others complain of failure. People who are successful continually do things on a daily basis that will increase their chances of success. Whilst unsuccessful people do not. This does not mean successful people never fail, they do. But what they don't do is give up, because they have developed habits of success. Change Your Habits! The message you should take away from this article is that in order to experience change in your life you must first identify your habits. Think about what do you do on a daily basis, and ask yourself if those things help you achieve what you want in life? If the answer is no, you must then change those habits, because by doing the same thing over and over again you will only get the same results over and over again. --- # Saying Yes URL: https://www.ivanturkovic.com/2013/03/16/saying-yes/ Published: 2013-03-16 Topics: Career & Leadership, Startups & Business "All that man achieves, and all that he fails to achieve is a direct result of his own thoughts." -James Allen John Lennon, at the height of his popularity, had his heart captured by one simple word: yes. He was rich, talented, desired and loved by people all over the world and had the option of nearly any woman he wanted. And 'yes' was what did it for him. He attended an exhibit in England and Yoko Ono was one of the artists. The piece in question was a ladder which led to a tiny sign and a magnifying glass. In order to see what was written on this tiny sign, one had to climb the ladder and look through this magnifying glass. It said, 'Yes'. It started with a yes. John later said he liked the piece so much because of it's positive message. He said if it had been something negative, he wouldn't have found Yoko or her work interesting in the least. We all say 'yes' or derivatives of it every day (yeah, uh huh, ya). A positive response to a question or situation. Yes. When we say yes, many times we are projecting an attitude and an affirmation, more than just agreement, and we are showing an openness and welcoming. As a persuader, I love to hear 'yes'. Self awareness and attitude go hand in hand with persuasion. This requires that you "check in" with yourself and pay special attention to all the signs and feelings we experience. There's a young women who works at the cafe where I work sometimes. She has the ultimate 'people suck' attitude. 'I'll never get what I want. Why can't I ever find a parking spot. I'm so fat. I'm always broke. . .' Wow. This is not a 'can do' attitude. She is most certainly not saying 'yes'. By changing our loops, our negative tapes, for positive ones, and you'll be rewarded immeasurably. It takes a lot of conscious work and can sometimes be uncomfortable. There have been days when I let negative moods get the better of me. Sometimes it happens without you even knowing it. I'm not normally a complainer. I take action when action is warranted, but I had a bad day not too long ago where I was irritated by absolutely everything. I noticed the ugly paint job on the house down the street, I noticed the horrible driver in traffic. And then something happened that interrupted my (temporary) pattern of negativity. Someone just said, 'Are you okay?' It made me realize that, yeah, I had really been complaining a lot, it had been a bad day. I took a deep cleansing breath, checked in with myself, and intentionally readjusted my attitude. Presto! That ugly paint job was inconsequential. Life happens. Part of life includes 'bad' things. We have the choice to respond with either positivity or negativity. Emotions are choices. Charles Hannel, author of 'The Master Key System', (an incredible study in self-improvement and higher consciousness) said of attitude, "The predominant thought or the mental attitude is the magnet, and the law is that like attracts like, consequently, the mental attitude will invariably attract such conditions as correspond to its nature." As you begin to reorient yourself to a 'yes' attitude, you will get more 'yes' in return. --- # Conquering Fear URL: https://www.ivanturkovic.com/2013/03/06/conquering-fear/ Published: 2013-03-06 Topics: Career & Leadership What really keeps you from living your dreams? What problem is most dominant in peoples lives? The answer is: FEAR! People live every day in fear. Fear of losing their wealth, fear of losing their loved ones, fear of making the wrong decisions, fear of being themselves, fear of growing up, fear of making a commitment. The list goes on and on. The leading cause of people not fulfilling their dreams is NOT the fear of failure - itís the fear of success! The fear of actually accomplishing what they set out to do. The fear of living life to the fullest may have paralyzed you. This will cause you to never really try in your business, or if you do try, to sabotage your efforts so you never have to face your fear of success. Most people live their lives in the grip of this fear and they arenít even aware that it has control over them! The fear is the one thing that can turn your dreams of financial freedom, loving relationships, and a fulfilling and significant life into a pattern of habits including procrastination, self-sabotage and other bad habits. Fear is the dominate problem in your life today. The two questions you need to answer to conquer your fear are: Which fear has the most control over your behavior? Is it the fear of failure, the fear of rejection, the fear of success, or is it all of them? How do I interrupt the bad habits that I have developed as a means of protection from this fear? How do I interrupt the programming I have within me? These are the two most significant questions when it comes to overcoming your fears. If you can answer these two questions, your life will forever change! The reality of fear is that it is human and is a part of life. Itís not going to go away. Some fear is even healthy! It is a gift given to you to keep you safe and bring you closer to your creator. Every person is born with three instinctive fears. These are: fear of falling, fear of loud noises, and fear of abandonment. These three fears were given to you to help you monitor what is going on around you. Think about it; it is fear that gives you the adrenaline rush that makes you escape from a situation that is really, truly unsafe. It also gives you the same rush that causes you to fight to win. Faith is born in fear. God knew in His infinite wisdom that fear is what would drive His people back to Him. Although sometimes it is only in times of extreme fear that we look to God and choose Him. Take a look at your own life and think about the times you looked to God for His power and wisdom. Those were probably times of extreme fear. True faith is born from fear! So what caused the gift of fear to be the number one problem in society today? Why do people let fear control their actions, beliefs and lives? The answer is the difference between reacting to fear and acting in fear. It has everything to do with your belief system. Children typically react instinctively to fear, which is appropriate behavior at this age. Most adults don't make the distinction between reacting to fear, as when they were a child, and acting with their intellect when dealing with fear. People react instinctively to fear by either denying it or running away from it. They miss the power that comes from acting with their intellect, never allowing the fear to become the gift it was intended to be. Learn to act with your intellect by exposing your true fears and the beliefs they represent and free yourself so you can move on with all you are meant to do, have and become!   --- # Creative Imagination URL: https://www.ivanturkovic.com/2013/02/19/creative-imagination/ Published: 2013-02-19 Topics: Career & Leadership Creative imagination is more than just active imagination. To be able to actively imagine things, to see and hear things in one's mind, is an important ability. It doesn't have to involve much creativity, though, does it? Daydreaming, for example, is a process of imagination. It can consist of an elaborate fantasy world, but one full of all the things that many people think about. Creative imagination, then, has to include the ability not just to imagine things, but to imagine original things. It is seeing things that others don't see, and coming up with new ideas. So how do you cultivate this? Creative Imagination 101 First, exercise your basic imagination. It can be as simple as thinking in pictures more, or listening to music in your mind. Play little "movies" in your mind, until you can watch them on command. This is a simple process, but for those of us that can't easily do it naturally, it can take a lot of practice. Fortunately, it is not an unpleasant activity. The second part of developing your creative imagination is to get more creative in your thinking and imagining. Start by paying attention to your creativity. Our subconscious minds give us more of what we pay attention to. Ignore creative aspects of your life, and you're telling your subconscious they are unimportant. On the other hand, if you note when you're creative, your subconscious mind will start feeding you more creative ideas. Different surroundings can also encourage your creativity. Want more creativity in your love life? Hike up a mountain with your partner. Do you write? Try sitting on a roof to write. Want new ideas for your business? Take a notebook to the park and sit by the duck pond. A change of environment can get your thinking out of it's ruts. You can play games that exercise your creative imagination. One such game uses a technique called "concept combination." Alone or with other players, you combine random concepts or things in new ways, to see who has the best idea. A thermometer and a billboard, for example, could generate an idea for a sign that checks the weather and adjusts the message accordingly ("Come in out of the heat for a cold beverage," or "Come in out of the rain and warm up with our gourmet coffee."). Don't Wait For Creative Imagination Creative inspiration certainly can strike at any time, but it strikes more often when there is work instead of waiting. So if you want to come up with creative inventions, start mentally redesigning everything you see. Imagine a better bicycle, a faster mail service, or a better chair. Continue this for three weeks, and it will become a habit. Of course, creative imagination goes beyond solving specific problems or inventing things. Truly creative minds are always coming up with the questions too, not just the solutions. If you want to be more creative all the time, focus on three things: 1. Changing your perspective. A child might think that working just to not work (to retire) is silly. Thinking from that perspective might give you ideas for how to make money doing things you enjoy. Seeing the world as a bear sees it might give a painter imaginative new ideas. Looking at things from a customer's perspective is a sure way to find creative improvements for a business. See everything from several perspectives. 2. Challenging your assumptions. What if restaurants didn't have employees? Visitors pay a machine as they enter, feed themselves at a buffet, and everything is as automated as possible, so one owner-operator could run a large restaurant alone. Challenge all your assumptions for practice. Do you really have to pay rent? Do swimming pools need water? Can exercise be a bad thing? 3. Let your ideas run wild. Does a flying bed seem silly? It could lead to the concept of a helium mattress. When you get off it in the morning, it floats out of the way, up to the ceiling. Perfect for small apartments. Don't stifle your creativity. Relax, and let ideas come. You can always discard them later. For these techniques to be a habitual part of your thinking, use them regularly. Since it takes several weeks to develop a habit, remind yourself to use them each day. Jot a few of your favorite techniques on a card and carry it with you. Look it over throughout the day and apply the techniques to anything. Soon, you'll have a more creative imagination. --- # Be A Leader Not A Follower URL: https://www.ivanturkovic.com/2013/02/15/91/ Published: 2013-02-15 Topics: Career & Leadership Most people in life are happy to follow the lead of other people, to sit on the fence in a debate or have the attitude of hiding at the back of the class. The most successful people are leaders and make things happen for themselves by taking a positive attitude and through working very hard to reach their goals. We are all able to become leaders and this article may help you to achieve this status. I grew up as a very nervous person, who had very little self-confidence and who was very shy. I was not happy being this way and often felt jealous of other people who were happy to speak up and take control of tasks and situations. I wanted to be one of these leaders and not the follower that I certainly had become. In my early twenties, I decided that the time had arrived to make a series of life changing decisions. I was determined to have a happy and successful life and was aware that I needed to change my whole attitude and approach to life. I was not happy at where I worked and joined an employment agency in an attempt to find alternative employment. In the reception area of the agency, I started speaking to a man who was also their hoping to find a different job. His name was Mark and we got on well and wished each other the best of luck in our quest to find work. We even exchanged phone numbers and vowed to keep each other up to date with our progress. I was then interviewed by a man who explained his plan to help me to find this new role of work. He was very bright, clever and positive and had many interesting ideas. He seemed very confident that I would soon be employed for a different company more suited to the skills which I had. At one stage of the meeting he described the scenario of a group interview. He suggested that we may be ushered into a large room with a big table. As we sat down there would be a sheet of paper asking the group to debate a particular subject. He stated that all of the people in the room would be in the same position, all looking for work, all nervous, however that in that room I could be whoever I wanted to be. He said that I needed to stand up and take the lead by stating that I would act as chairman and that if anyone had a comment that they wanted to make, that they could raise their hand. If I did this I would show my prospective employer that I was a leader. I went home and thought about this and did not believe I had it in me to act in the way that he wanted me to, as I was not a leader. Later that evening Mark phoned and all he was talking about was the group interview scenario. He also stated that there was no way that he would be able to stand up either in the way that had been described. I did not have to ever attend a group interview, but Mark did. He surprised himself by carrying out the advice and stated that after he had said his initial statement about being the chairman, that he had never felt so powerful and in control. For the rest of the day other members of the group had been regularly asking him questions as if he was some sort of team leader. He was very proud of himself and I am happy to report that he was successful at that interview and is very happy in his new position. He has also taken the success and learning experience into his social life and states that he has never had a higher self-esteem. I am very pleased for him as he is a genuine person who works very hard. He is also one of only a few friends that I can honestly say that I trust. I have taken inspiration from Mark and have entered into a career of helping people who stutter, to achieve fluency. This is something which I really enjoy and which gives me a large amount of job satisfaction. I am also far more confident than I ever have been and have at last found happiness. In conclusion we all have it in us to be a leader. We need to be brave and determined to take control and remember that all we can do in life is to try our best. We are only sure about living one life, so lets be happy and not accept second best. Be a leader in life, not a follower.   --- # 20 Ways To Make Someone Smile URL: https://www.ivanturkovic.com/2013/01/16/20-ways-to-make-someone-smile/ Published: 2013-01-16 Topics: Career & Leadership Do you want to put a smile on someone's face today? Maybe make their day a little better. It won't cost you a single penny or much time to do just that. And because smiling is contagious, it probably won't just be one person you make smile today. Here are 20 ways to turn that frown upside down. 1. Send some flowers to your partner at work. 2. Compliment a friend or work colleague on their appearance. 3. Donate something to charity. 4. Take a friend out to lunch. 5. Let someone know you miss them. 6. Make a surprise telephone call to your partner at work, just to say hi. 7. Hold a door open for someone walking behind you. 8. Hug your partner for no reason. 9. Leave a joke on a friends answer machine. 10. Send a card to a friend letting them know what a good friend they are. 11. Give up your seat on the train to someone when there aren't any left. 12. Share your umbrella on a rainy day. 13. Ask a friend if they need anything while you're out shopping. 14. When it's raining, plan an indoor picnic with your children. 15. Leave a love letter somewhere where your partner will find it. 16. Send someone an unusual and unexpected gift like chocolate, flowers or sex toys. 17. Tell your child you're proud of them. 18. Tell someone you thought about them the other day. 19. Cook a surprise meal for your partner one night, especially if they normally do the cooking. 20. Tell your partner you love them. Did you know it takes only 17 muscles to smile, but 43 to frown. Why waste all that energy frowning when you could just smile. Here are 5 more facts about smiling. 1. Women smile more than men. 2. Smiling releases endorphins that make us feel better. 3. We are all born with the ability to smile, it's not something we learn from others. 4. A smile is a universal expression of happiness. 5. A smiling person is thought to be a more pleasant, attractive, sociable, sincere and competent than a non-smiling person. Just remember, smiling is the easiest and cheapest way of improving your looks.   --- # How to Sell to Anyone URL: https://www.ivanturkovic.com/2012/04/04/how-to-sell-to-anyone/ Published: 2012-04-04 Topics: Startups & Business Let's face it. We all have those difficult customers to whom we are required to sell. From the demanding, abrasive buyer to the individual who never seems to make a buying decision, we encounter challenging people on a regular basis. Part of the reason this happens is due to the disconnect we have because of conflicting personalities. This post will look at the four key types of people and how to improve your results with each. Direct Donna. Donna is very direct in her approach. She tends to be forceful and always wants to dominate or control the sales call. Her behaviour is aggressive, she points at you while she talks, interrupts your to challenge you, and she seldom cares about hearing the details of your new product or service. Instead, she demands that you cut to chase and tell me the bottom line. Donna is very results-focused and goal-oriented and hates wasting time. To achieve the best sales results with this individual you need to be more direct and assertive. Tell her at the beginning of the sales call or meeting that you know how busy she is and how valuable her time is. Tell her that you will get right to the point and focus your conversation on the results she will achieve by using you product or service. Resist the temptation to back down if she confronts you because you will lose her respect. To Donna, it is not personal, it's just business. Lastly, be direct in asking for her business, you don't have to dance around this issue. Talkative Tim. Tim is a gregarious and outgoing person but very ego-centric. He is often late for your meetings and his constant interruptions and long stories cause your sales calls to go beyond the scheduled time. He appears to be more concerned with listening to himself talk which is frustrating because you don't always get enough time to discuss your solution. Relationships are very important to Talkative Tim so invest more time in social conversation. Even if you don't see the point in this, he will appreciate the gesture and will like you more. This person often makes buying decisions on intuition and how he feels about the sales person. Be careful not to challenge Tim because he will feel rejected and when this happens he will shut down and become unresponsive. During your sales presentation, tell him how good your solution will make him look to others in the company or how his status or image will improve. In other words, appeal to his ego. Steady Eddie. Soft-spoken, Eddie is a nice fellow who seems more focused on his team and coworkers than on his personal results. He is very quiet compared to some of your other prospects and can be difficult to read. But most frustrating is his reluctance to make a buying decision. Eddie's mantra seems to be I'm still thinking about but thanks for following up. Structure and security is important to these people and it is difficult for Eddie to make changes. He often contemplates how the decision will affect other people within the organization. That means you need to slow down the sales process, demonstrate how your solution will benefit the team, and remove as much risk from the decision-making process as possible. Soften your voice and make sure your sales presentation flows in a logical manner. Use words like fair logical and your team in your presentation. Analytical Alice. She reads every point and specification about your product or service and regardless of how much information you give Alice, she always wants more, including written guarantees and back up documentation. She is very difficult to read and it is extremely difficult to get her engaged in an open conversation because personal feelings and emotions do not enter the picture when Alice makes a decision. Whenever possible, give Alice a written, bullet-point agenda of your meeting beforehand. Ideally, email it to her a few days in advance so she can prepare herself. Make sure it is completely free of typos, spelling mistakes and punctuation errors. When you meet, follow the agenda in perfect order and if you make any type of claim, have supporting documentation available for her to read. While the approach to use with each of these people may not make sense to you or seem completely rational, it is critical to recognize that how you naturally and instinctively sell may not be the best way to get results with someone else. Modifying your approach and style, even briefly, will help you better connect with your customers and prospects which means you will generate better sales. --- # Install Compass in an existing Rails application with Semantic Blueprint using Sass URL: https://www.ivanturkovic.com/2011/10/23/install-compass-in-an-existing-rails-application-with-semantic-blueprint-using-sass/ Published: 2011-10-23 Topics: Ruby on Rails compass init rails --using blueprint/semantic --syntax sass --- # Capistrano + RVM = Problem with deployment? URL: https://www.ivanturkovic.com/2011/10/18/capistrano-rvm-problem-with-deployment/ Published: 2011-10-18 Topics: Ruby on Rails, Software Engineering Most of the time when I had issues with deploying on rvm environment it helps you to do the following. Create a file in $home folder touch .rvmrc And paste following code: rvm_trust_rvmrcs_flag=1 --- # Working OAuth2 with Foursquare on Sinatra URL: https://www.ivanturkovic.com/2011/09/19/working-oauth2-with-foursquare-on-sinatra/ Published: 2011-09-19 Topics: Ruby on Rails, Software Engineering require 'rubygems' require 'sinatra' require 'oauth2' require 'json' require 'net/https' require 'foursquare2' set :port, 80 CLIENT_ID = '****************************************************' CLIENT_SECRET = '****************************************************' CALLBACK_PATH = '/callbacks/foursquare' def client OAuth2::Client.new(CLIENT_ID, CLIENT_SECRET, {:site => 'https://foursquare.com/', :token_url => "/oauth2/access_token", :authorize_url => "/oauth2/authenticate?response_type=code", :parse_json => true, :ssl => {:ca_path => '/etc/ssl/certs' } }) end def redirect_uri() uri = URI.parse(request.url) uri.path = CALLBACK_PATH uri.query = nil uri.to_s end get CALLBACK_PATH do puts redirect_uri if params[:code] != nil token = client.auth_code.get_token(params[:code], :redirect_uri => redirect_uri).token client = Foursquare2::Client.new(:oauth_token => token) email = client.user('self')['contact'].email.to_s return "Authenticated user: #{email}" else 'Missing response from foursquare' end end get '/' do redirect client.auth_code.authorize_url(:redirect_uri => redirect_uri) end --- # Latest eclipse app on Mac OS Lion preference pane URL: https://www.ivanturkovic.com/2011/09/12/latest-eclipse-app-on-mac-os-lion-preference-pane/ Published: 2011-09-12 Topics: Software Engineering, Tools When upgrading to a new eclipse app and selecting existing workspace it makes preference pane unavailable. So to be able to access settings you have to either create a new workspace and migrate projects to a new workspace or remove settings files from old one. This saved my time a couple of times. --- # Mac OS Lion and installing ruby 1.9.2 URL: https://www.ivanturkovic.com/2011/09/08/mac-os-lion-ruby-1-9-2/ Published: 2011-09-08 Topics: Ruby on Rails I am always having trouble with installing fresh rvm instance, so here are all steps needed in fresh rvm to install new ruby 1.9.2 export ARCHFLAGS="-arch x86_64" export CC=gcc-4.2 rvm pkg install readline rvm pkg install iconv rvm pkg install zlib rvm install 1.9.2 -C --with-readline-dir=$rvm_path/usr --with-iconv-dir=$rvm_path/usr --with-zlib-dir=$rvm_path/usr --- # Kindle still rules the ebook world URL: https://www.ivanturkovic.com/2011/06/19/kindle-still-rules-the-ebook-world/ Published: 2011-06-19 Topics: Tools First time when i saw kindle being presented I was thrilled, because i read a lot of books. I saw a future in electronic books. That was 2 years ago and I wasn't wrong. Nowadays Amazon sells more ebooks than paper print books. So last year when they introduced third generation of Kindle, I've jumped on the kindle wagon. I've bought wifi only version which came quite cheap. I never imagined that kindle would replace all paper books. Actually since owning kindle there was only one book I've bought and that is Four steps to epiphany. The only reason I've bought it is that is not available in digital form. Does this mean kindle replaced all paper based books for me? Well yes and no. There is still one thing that kindle cannot be used and that are pdfs, even it supports them they are unusable on it. Zooming functionality is not quite there. Only competitor To kindle wasn't other ebook readers but Apple iPad. I was quite known to it, even tried it few times, but it was obvious that it is unusable in direct sun light. But still I was wondering if I would be able to use it for reading books. So last month I've finally needed to buy iPad, because i was doing some applications for it. So I also tried to use it as a ebook reader, because it also has kindle app so it means all books i've bought before i could them read on it also. My final decision is that it didn't came to my expectations as a ebook reader. Only part it did excel were pdfs technical stuff which I wasn't able to read on kindle. So to write a conslusion. I will use both devices as a reading utility but kindle will still be my main device for reading. No other digital device can beat reading in direct sun light. But main differentiator is that kindle still feels like something natural it doesn't have exclusive electronic device feeling. It feels like something coming from analog age. I even don't have a feeling I need to take care of it because it feels as rugged device. From mine point of view I can easily say that kindle has a bright future as a reading device and I hope they will continue and not to become yet another android clone as Nook did. --- # The inception URL: https://www.ivanturkovic.com/2010/12/01/the-inception/ Published: 2010-12-01 Topics: Startups & Business It's time to start over, blogs do go old over time and we don't want to read old topics or out of date blog.  So i started from the beginning. For all who doesn't know me I am mostly interested into technology, art and other modern esoteric interests. So stay tuned for my posts.