DEV Community

Mihai-Alexandru Soare
Mihai-Alexandru Soare

Posted on

Is the joy of programming dead? We debate losing jobs, but we are definitely losing satisfaction.

Introduction

The practice of typing out code at the keyboard is losing popularity. In today's
era, we rarely write a feature from scratch, but rather generate functions and
modules and the like, then iterate over that, either by hand or through more
prompting. Regardless of whether you touch up on the code manually or end up
getting a good enough result only by prompting, you lose the capacity to style
the code with the granularity you would have had had you written the code on
your own. This matters less in routine features like a CRUD endpoint in a
project where the overall code style is established, but I argue that for small
scope implementations, like a single function or class, there is some joy in
bootstrapping them like the old times, a practice that is no longer justified in
the current market.

In the coming paragraphs, I will present a DSA problem I overengineered on
accident, the strong points that the implementation had over the book approach,
and why it makes even less sense in the present day to ship an implementation
like that (at least from the business perspective), now that we have AI tools.

Before getting to the next section, I would like to present myself in order to
give some context about my background and interests. I am a computer science
student in my 2nd year of university. I worked for a short 6 months at a
consulting company + some 4-week internships at that very same company. I got
into programming at the young age of 12 because I was and remain passionate
about creating things, but I have always felt a stronger connection to the
practice of building and the craft itself than to the finished product.

Unconventional solution I got to

As I was preparing for coding interviews, I was using the 'select random'
feature on LeetCode and stumbled over this basic string problem: Find the
longest substring without duplicated characters.

One approach to this problem is to keep two pointers of a sliding window, and
also a hash set of the characters in that window in order to check for
duplicates in O(1) time when advancing a pointer. In plain English, the
algorithm works as follows. Take two indices left, right = 0, and a hash set s =
{}. While the window is in bounds of the string, increment the right pointer. If
the letter that joined the window is a duplicate, you then start to increment
the left pointer until its duplicate is no longer part of the window. The
solution is the maximum width the window had during the slide. The only little
caveat is you had to keep the set in sync with the pointers.

Here is the first thing I typed on LeetCode:

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        l = 0
        r = 0 # non inclusive
        letters = set()

        def eject_letter(letter: str):
            if len(letter) != 1:
                raise ValueError()

            if letter not in letters:
                return

            nonlocal l, r
            while l < r:
                lt = s[l]
                letters.remove(lt)
                l += 1

                if lt == letter:
                    break

        def expand_window():
            nonlocal l, r

            if r >= len(s):
                raise StopIteration()

            lt = s[r]
            eject_letter(lt)
            letters.add(lt)
            r += 1

        def sizes():
            try:
                while True:
                    yield len(letters)
                    expand_window()
            except StopIteration:
                pass

        return max(sizes())
Enter fullscreen mode Exit fullscreen mode

It is influenced by the OOP idea of encapsulating the state and implementing an
API that protects the invariants of the enclosed state (I will explain this in
detail later). The only thing is that instead of a class in the same namespace,
I use closures.

This is the most direct way that I could turn the semantics of the algorithm
described above into Python syntax.

I also asked Gemini to generate a solution to the problem:

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        seen = set()
        left = 0
        max_len = 0

        for right, char in enumerate(s):
            while char in seen:
                seen.remove(s[left])
                left += 1
            seen.add(char)
            max_len = max(max_len, right - left + 1)

        return max_len
Enter fullscreen mode Exit fullscreen mode

I am not a fool, alright? I knew that my solution is on the higher end when it
comes to line count, but I didn't expect the book solution to be this compact.
And honestly, besides some vertical padding I would add between the shrinking,
growing, and computing of max_len, this is what I would ship in practice.

All in all, there are still some strong points to my implementation:

  1. The state is mutated by the trivial shrinking and growing closures. Both implementations make the assumption that the hash set contains the characters in the window. But in the AI implementation, this is only true after seen.add(char) is used. In theory, if you had to modify the code, you could overlook this caveat and modify a pointer without updating the set and vice versa. One benefit of encapsulating the state with closures is that the invariants in the state are preserved in the code that calls those closures.
  2. The steps of the algorithm are naturally labeled and isolated. It is very apparent that we are taking the maximum of the lengths as we grow the window and preserve the no-duplicate constraint.

Now pretend that typing out code was the only way to implement such a simple DSA
problem. The time it takes to implement the two are very close, given that for
the book solution, I would first have to think about how all the logic comes
together in that loop and validate that indeed it works as I originally
expected. Could you justify opening a PR with the more verbose implementation?
Well, you could counter point 1 by arguing that the function is a very small
architectural unit, and you have unit tests at that level anyway, so even if
requirements changed and you had to modify the solution, it would be very
realistic to tear it down fully and start anew. But point 2 is still very much
true. You don't need to be familiar with the sliding window approach to quickly
follow what is happening in the implementation, it makes it easier to spot bugs
and validate conceptually before writing tests that the code does what it is
supposed to.

ASIDE: raising StopIteration, code smell?

The astute reader will have noticed the unconventional use of StopIteration
in the expand_window closure. The exception exists in order for use with
class based iterators and is only meant to be raised inside the __next__
method. Still, I decided to use it there because the name conveys exactly
what I meant to achieve. This sort of controversial decision is another
sign of human originality that an AI would not make, for better or worse.

It no longer matters anyway

Now we relax that unrealistic assumption. You prompt the AI and get a solution
that is good enough. In my opinion, the generated solution to this problem is
better than the one I originally wrote because I believe the simplicity of it
beats the benefits of mine that I listed above. Even still, there are cases
where I would have picked the other way around, so pretending that there is a
more optimal code you would have written yourself, you have a good enough
working solution passing acceptance criteria and tests. It is not worth it to
micro-optimize it to fit your expectations.

And there is another reason to lower the bar when it comes to implementations.
Humans are prone to human error. But AIs don't make the same types of mistakes;
an AI rarely makes a typo or forgets to update a variable. That being said, it
is easier to tolerate code of poor quality. It was shown that after the rise of
LLMs, teams refactor less frequently. [1]

Closing thoughts

It's a sad era for creatives, in the world driven by profit and in the presence
of generative AI, it doesn't always sell to be very nitpicky about your craft.
But it is not true for most hobbies that they also make a profitable career, and
I believe that we as software engineers still have it pretty good. After all,
coding is not all there is to software engineering; there is a lot of demand for
elegance in high-level solution architecture and auxiliary automation. While
handwriting is losing appreciation, there is still designing to be done at the
code level, picking which dependencies to use, what interface and how you test
it, etc. And it also may be that I am too pessimistic writing this article;
perhaps there are still contexts in which you really want to be nitpicky about
your implementation, after all, code comprehension is a requirement that isn't
going anywhere in the near future.

[1] AI Copilot Code Quality: 2025 Look Back at 12 Months of Data -
https://www.gitclear.com/ai_assistant_code_quality_2025_research

- Mihai-Alexandru Soare, soare.io

Top comments (0)