<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: José González</title>
    <description>The latest articles on DEV Community by José González (@jgongo).</description>
    <link>https://dev.to/jgongo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3813144%2F22d9b7ce-704b-4fbf-93bb-98bb81606a73.png</url>
      <title>DEV Community: José González</title>
      <link>https://dev.to/jgongo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jgongo"/>
    <language>en</language>
    <item>
      <title>Why Your AI Strategy Is Three-Quarters Wrong</title>
      <dc:creator>José González</dc:creator>
      <pubDate>Fri, 13 Mar 2026 10:38:45 +0000</pubDate>
      <link>https://dev.to/jgongo/why-your-ai-strategy-is-three-quarters-wrong-c0j</link>
      <guid>https://dev.to/jgongo/why-your-ai-strategy-is-three-quarters-wrong-c0j</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jef5mose7pnldrdezjp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jef5mose7pnldrdezjp.png" alt="cover.png" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everyone's talking about AI productivity. Ship faster. Write more code. Automate the boring stuff. Kill the developers (well, kind of). And sure, that's real — I've seen it myself, having built several Claude Code plugins for my team at Dow Jones. AI genuinely makes you faster.&lt;/p&gt;

&lt;p&gt;But here's the thing: if your entire AI strategy is "make developers more productive," you're focusing on one quarter of the picture and ignoring the three quarters that actually matter for the long-term health of your engineering organisation.&lt;/p&gt;

&lt;p&gt;Bold claim? Absolutely. Let me earn it.&lt;/p&gt;

&lt;h1&gt;
  
  
  The knowledge problem nobody talks about
&lt;/h1&gt;

&lt;p&gt;Before we get to AI, let me ask you something. Think about the best engineer you've ever worked with. The one who, when a production incident happens at 2 AM, somehow &lt;em&gt;knows&lt;/em&gt; where the problem is before anyone else has finished reading the logs. The one who reviews your pull request and spots the architectural issue you didn't even know existed.&lt;/p&gt;

&lt;p&gt;Now ask yourself: how much of what makes that engineer great is written down somewhere?&lt;/p&gt;

&lt;p&gt;If your answer is "not much," congratulations — you've just identified the most valuable and most fragile asset in your organisation. That engineer's knowledge is &lt;em&gt;tacit&lt;/em&gt;: it lives in their head, was built through years of experience, and is extraordinarily hard to articulate. It's the difference between knowing the syntax of a language and knowing &lt;em&gt;when not to use a particular pattern&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The counterpart is &lt;em&gt;explicit&lt;/em&gt; knowledge — the stuff that IS written down. Documentation, runbooks, architecture decision records, onboarding guides. The kind of knowledge you can hand to someone and say "read this."&lt;/p&gt;

&lt;p&gt;The central challenge of any engineering organisation — and this is not a new insight, by the way — is converting the first into the second. Because tacit knowledge, no matter how brilliant, walks out the door when that engineer takes a new job.&lt;/p&gt;

&lt;h1&gt;
  
  
  A model from 1995 that explains everything
&lt;/h1&gt;

&lt;p&gt;In 1995, Nonaka and Takeuchi published &lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/The_Knowledge-Creating_Company" rel="noopener noreferrer"&gt;The Knowledge-Creating Company&lt;/a&gt;&lt;/em&gt;, where they described something called the &lt;a href="https://en.wikipedia.org/wiki/SECI_model_of_knowledge_dimensions" rel="noopener noreferrer"&gt;SECI model&lt;/a&gt; (pronounced "SEH-kee," in case you were wondering — I certainly was). It describes four modes of knowledge conversion that form a continuous spiral:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Socialization&lt;/strong&gt; (Tacit → Tacit): you learn by sitting next to an expert. Pair programming, mentoring, the hallway conversation where someone casually drops a gem that saves you three days of debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Externalization&lt;/strong&gt; (Tacit → Explicit): someone writes down what they know. Design docs, ADRs, that one Confluence page that actually explains how the payment system works, or that script that automates a repetitive established workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combination&lt;/strong&gt; (Explicit → Explicit): you synthesise scattered knowledge into something structured. Wikis, knowledge bases, that quarterly report that pulls together insights from five different teams.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internalization&lt;/strong&gt; (Explicit → Tacit): you read the docs and, through practice, develop genuine understanding. Not just knowing &lt;em&gt;what&lt;/em&gt; the architecture is, but &lt;em&gt;feeling&lt;/em&gt; why it has to be that way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These four modes form a spiral because the output of each phase feeds the next. Tacit knowledge gets externalised, combined with other knowledge, internalised by new people, who then socialise it further through their own practice. Round and round it goes, each turn creating new knowledge that didn't exist before — which is why Nonaka called it knowledge &lt;em&gt;creation&lt;/em&gt;, not knowledge &lt;em&gt;transfer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You can also think of it as two complementary movements: &lt;strong&gt;capture&lt;/strong&gt; (Externalization + Combination) makes knowledge available &lt;em&gt;to the organisation&lt;/em&gt;, while &lt;strong&gt;spread&lt;/strong&gt; (Socialization + Internalization) gets knowledge &lt;em&gt;into people's heads&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2rwdky5247mgzf89mvi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2rwdky5247mgzf89mvi.png" alt="seci-spiral.png" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"That's a nice academic model," you might say. "But what does it have to do with AI?"&lt;/p&gt;

&lt;p&gt;Everything.&lt;/p&gt;

&lt;h1&gt;
  
  
  AI disrupts every phase (and not equally)
&lt;/h1&gt;

&lt;p&gt;Here's where it gets interesting. AI — and specifically LLMs — disrupts every single phase of the SECI spiral, but the impact is wildly uneven:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SECI Phase&lt;/th&gt;
&lt;th&gt;AI Role&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Socialization&lt;/td&gt;
&lt;td&gt;AI as synthetic pair partner&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Externalization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;AI as articulation partner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Transformative&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combination&lt;/td&gt;
&lt;td&gt;AI as synthesis engine&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internalization&lt;/td&gt;
&lt;td&gt;AI as practice partner&lt;/td&gt;
&lt;td&gt;Mixed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnd5nvd8swjeze18qdcxg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnd5nvd8swjeze18qdcxg.png" alt="ai-disrupts-seci.png" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The biggest impact is on &lt;strong&gt;Externalization&lt;/strong&gt; — the historically hardest conversion. Ask any senior engineer to write down what they know, and you'll get a mixture of procrastination, vague hand-waving, and a half-finished Confluence page abandoned three months ago. It's not that they're lazy (well, maybe, I must admit that I may hate like hell writing documentation, but just may) — it's that articulating tacit knowledge is genuinely difficult. You know more than you can tell, as &lt;a href="https://en.wikipedia.org/wiki/Michael_Polanyi" rel="noopener noreferrer"&gt;Polanyi&lt;/a&gt; put it.&lt;/p&gt;

&lt;p&gt;AI changes this equation dramatically. You can now think out loud with an LLM, and it helps you structure your vague understanding into coherent, shareable text. It's like having a patient interviewer who asks the right follow-up questions and turns your stream of consciousness into an architecture decision record. The friction of externalization drops by an order of magnitude.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Combination&lt;/strong&gt;, AI serves as a synthesis engine that can search, cross-reference, and recombine explicit knowledge across silos. That information buried in a JIRA ticket from six months ago, connected to a design doc in Confluence, connected to a Slack thread that nobody bookmarked? An AI with the right context can pull those threads together. This is the "boring but transformative" category — not glamorous, but it solves the very real problem of knowledge scattered across fifteen different tools.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Socialization&lt;/strong&gt;, AI pair programming creates a new form of working alongside a knowledgeable colleague. It's moderate because it lacks the richness of genuine human interaction — but it's available at 2 AM when your actual colleagues are, sensibly, asleep (as you'd probably be too, by the way).&lt;/p&gt;

&lt;p&gt;And for &lt;strong&gt;Internalization&lt;/strong&gt;... well, this is where it gets complicated. AI can accelerate learning by explaining concepts, generating exercises, and providing on-demand tutoring. But it can also short-circuit the learning process entirely. If the AI writes the code and you just accept it, you never develop the tacit understanding that makes you effective. More on this in a moment.&lt;/p&gt;

&lt;h1&gt;
  
  
  The fourfold strategy (or why you're focusing on the wrong quarter)
&lt;/h1&gt;

&lt;p&gt;Given how AI disrupts the spiral, I think the strategic response has to be fourfold:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Build knowledge infrastructure&lt;/strong&gt; — create shared tools and agents that encode team expertise and lower friction across both capture and spread. Skills that automate workflows while documenting &lt;em&gt;why&lt;/em&gt; those workflows exist. Agents that connect to your wiki, your issue tracker, your codebase, and synthesise across them. This isn't about a specific tool — it's about building the infrastructure for the entire SECI spiral to turn faster. This is just the enabling layer for the other three goals — infrastructure without deliberate practice is just an empty pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Boost productivity&lt;/strong&gt; — the obvious one. Use AI to synthesise internal and external knowledge to accelerate work. This is where 99% of the conversation is today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Capture senior expertise&lt;/strong&gt; — use AI as an articulation partner to help senior developers externalise their tacit knowledge. That architect who "just knows" how the system should evolve? Sit them down with an AI and help them turn that intuition into documentation, ADRs, and design principles that outlive their tenure. This is Externalization, and it's arguably &lt;strong&gt;more valuable than productivity&lt;/strong&gt; — because it creates the knowledge base that makes productivity possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Upskill junior developers&lt;/strong&gt; — the most neglected goal, and the one I feel most strongly about. AI should be deliberately used to accelerate Internalization: helping juniors build the deep tacit knowledge they need to become the strong engineers who make AI effective. Not just making them productive today, but growing their actual understanding.&lt;/p&gt;

&lt;p&gt;Here's the uncomfortable truth: &lt;strong&gt;almost everyone jumps straight to #2 without building the infrastructure that makes everything else possible, and almost nobody has a strategy for #4.&lt;/strong&gt; The absence of an upskilling strategy is itself a strategy — it defaults to deskilling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Lisanne_Bainbridge" rel="noopener noreferrer"&gt;Lisanne Bainbridge&lt;/a&gt; identified this paradox back in 1983 in a paper called &lt;em&gt;&lt;a href="https://doi.org/10.1016/0005-1098(83)90046-8" rel="noopener noreferrer"&gt;Ironies of Automation&lt;/a&gt;&lt;/em&gt;: the more we automate, the more we depend on highly skilled operators — whose skills are degraded by the automation itself. If juniors never struggle with problems because AI solves them, they never develop the tacit knowledge that makes senior engineers effective. And if we deplete that pipeline, we lose the very capacity to direct AI well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvb5ocfjnqcnrabv0q7t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvb5ocfjnqcnrabv0q7t.png" alt="bainbridge-paradox.png" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The four goals are interdependent. Infrastructure (#1) enables everything but is useless without the people to fill it. Productivity (#2) depends on having externalised knowledge to draw from (#3). And upskilling (#4) ensures the pipeline of engineers who can do all three.&lt;/p&gt;

&lt;h1&gt;
  
  
  The dark side (because of course there is one)
&lt;/h1&gt;

&lt;p&gt;Every one of these disruptions has a flip side, and I'd be dishonest if I didn't mention them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hidden explicit knowledge&lt;/strong&gt;: your tools, agents, and plugins work beautifully — but nobody understands &lt;em&gt;why&lt;/em&gt; the decisions embedded in them were made. The workflow rationale becomes invisible once it "just works." This is Bainbridge's irony applied to AI infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;False externalization&lt;/strong&gt;: AI produces text that &lt;em&gt;looks&lt;/em&gt; like expert knowledge but is actually hallucinated. If that enters your knowledge base unchecked, you now have institutional misinformation that nobody questions. That's worse than having no knowledge at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context poisoning&lt;/strong&gt;: if you build a knowledge system (as I argue you should), bad context compounds just like good context. A stale note about a reversed decision will confidently lead the AI — and your team — astray.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge homogenization&lt;/strong&gt;: if everyone uses the same AI with the same context, you risk everyone converging on a single way of thinking. The SECI spiral needs diverse tacit input; shared AI context can suppress that diversity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The curator burden&lt;/strong&gt;: knowledge systems need maintenance. If nobody reviews, prunes, or updates them, you've just moved the graveyard of stale documents from Confluence to a new tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn0gf5ut7s0as468v0ts7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn0gf5ut7s0as468v0ts7.png" alt="dark-side.png" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The common thread? None of these are solved by technology alone. They all require human judgment, review rituals, and active stewardship. The same engineers you're trying to boost with AI need to be the ones keeping the knowledge system honest.&lt;/p&gt;

&lt;h1&gt;
  
  
  So what now?
&lt;/h1&gt;

&lt;p&gt;If you've read this far — first of all, thank you for your patience. Let me leave you with the thought that prompted this entire article.&lt;/p&gt;

&lt;p&gt;The question isn't whether AI will change how knowledge moves through your engineering organisation. It already is. Every time a developer uses an AI assistant, they're participating in the SECI spiral — creating knowledge, consuming knowledge, sometimes creating &lt;em&gt;false&lt;/em&gt; knowledge. This is happening whether you have a strategy for it or not.&lt;/p&gt;

&lt;p&gt;The question is whether you shape that deliberately, or let it happen by accident.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1wnwf9sy3mk7vqyi7ky8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1wnwf9sy3mk7vqyi7ky8.png" alt="rusty-gears.png" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A productivity-only AI strategy is like optimising one gear in a four-gear machine. It'll spin faster, sure. But if the other three gears are rusty, the machine doesn't move.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>knowledgemanagement</category>
      <category>secimodel</category>
      <category>engineeringstrategy</category>
    </item>
    <item>
      <title>Why You Are Wasting Your Time Making Your Code Performant</title>
      <dc:creator>José González</dc:creator>
      <pubDate>Sun, 08 Mar 2026 17:09:11 +0000</pubDate>
      <link>https://dev.to/jgongo/why-you-are-wasting-your-time-making-your-code-performant-3i8a</link>
      <guid>https://dev.to/jgongo/why-you-are-wasting-your-time-making-your-code-performant-3i8a</guid>
      <description>&lt;p&gt;I'm sure you may have come across some post in LinkedIn comparing &lt;em&gt;junior&lt;/em&gt; vs &lt;em&gt;senior&lt;/em&gt; code. They are unmistakable, with that fresh morning smell of heated arguments and even holy wars, with endless comments arguing why some option is better than another option or how something runs 1% faster.&lt;/p&gt;

&lt;p&gt;Sometimes they favour highly performant code, other times they favour much shorter code, usually including some nerdy or obscure feature of the language being used. These snippets of code are shared to show you how senior the original poster is, seniority being directly proportional to the amount of those obscure features they know and inversely proportional to the length of their code.&lt;/p&gt;

&lt;p&gt;Some other times they discuss functional vs imperative approaches, arguing about the time and space complexity of the different solutions. These discussions offer endless insights about the vast knowledge of the senior developers involved in the discussion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculating the price of discounted products
&lt;/h2&gt;

&lt;p&gt;One of those posts showed the following snippets of code, comparing functional and imperative versions of the same functionality, apparently calculating the cost of certain discounted products, that you would only buy if they are more expensive than a certain amount of money. I have adapted the code to Scala from the original Javascript version.&lt;/p&gt;

&lt;p&gt;Let's assume you have the following data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Note to really senior developers, I'm aware that Double is not an&lt;/span&gt;
&lt;span class="c1"&gt;// appropriate data type for prices, just using it for the sake of simplicity&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;products&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Apple"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Banana"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Orange"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you have the functional version of the code (with the curried functions that weren't included in the original example):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;applyDiscount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
  &lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;priceGreaterThan&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculateTotalPrice&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstPrice&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secondPrice&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;firstPrice&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;secondPrice&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;totalPrice&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;applyDiscount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;priceGreaterThan&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;reduce&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calculateTotalPrice&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here you have the imperative version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;totalPrice&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="k"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;discountedPrice&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;discountedPrice&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="n"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;totalPrice&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;discountedPrice&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original poster, blatantly ignoring the &lt;em&gt;Hic sunt dracones&lt;/em&gt; annotation, candidly asked which option looked better, stating his mild preference for the functional version of the code for its improved readability and intention conveying.&lt;/p&gt;

&lt;p&gt;Of course the obvious happened: hell rained upon Earth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are you crazy? Functional code is O(3n)!
&lt;/h2&gt;

&lt;p&gt;One of the main discussions around the merits of each version came down to the performance of the code, with some senior developers arguing that the functional code had &lt;code&gt;O(3n)&lt;/code&gt; time complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why O(3n) isn't worse than O(n)
&lt;/h3&gt;

&lt;p&gt;First of all, let me clearly state this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;O(3n)&lt;/code&gt; is not a thing. Period.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reasoning behind this was that the functional code iterates three times through the products collection, one for &lt;code&gt;map&lt;/code&gt;, another for &lt;code&gt;filter&lt;/code&gt;, and a last one for &lt;code&gt;reduce&lt;/code&gt;. Meanwhile the imperative code only had one loop, iterating only once through the collection. So according to these senior developers, the functional code had &lt;code&gt;O(3n)&lt;/code&gt; time complexity, while the imperative code had &lt;code&gt;O(n)&lt;/code&gt; time complexity.&lt;/p&gt;

&lt;p&gt;First of all, I won't go into the nitty-gritty details of the mathematical definition of the Big-O notation, but suffice to say that this notation is usually used to define an &lt;em&gt;asymptotic&lt;/em&gt; behaviour when the input size tends to infinite. In the case of &lt;code&gt;O(n)&lt;/code&gt; we are saying that the time spent by the algorithm grows &lt;em&gt;linearly&lt;/em&gt; based on the size of the input (again this is a simplification). So it doesn't matter if the time function of your algorithm is &lt;code&gt;n/150&lt;/code&gt;, &lt;code&gt;n&lt;/code&gt;, &lt;code&gt;3n&lt;/code&gt;, or &lt;code&gt;12500000n&lt;/code&gt;, they all &lt;strong&gt;&lt;em&gt;grow linearly&lt;/em&gt;&lt;/strong&gt;, so they all are &lt;code&gt;O(n)&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Duh, but functional code still runs three times slower
&lt;/h3&gt;

&lt;p&gt;Really? Again, to keep things simple we won't take into account the nuances associated with modern computing and memory layering. Even without taking into account this, dereferencing a memory position in an array is usually a much quicker operation than anything that you do with that data inside the loop. If those operations are an order of magnitude greater than the dereferencing, the dereferencing operations become negligible.&lt;/p&gt;

&lt;p&gt;You don't believe me? Let's do some basic maths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let's say that you do three operations per loop, which take &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Let's say dereferencing a position in the input array takes &lt;code&gt;d&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The total time of the operation will be, for an input of size &lt;code&gt;n&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the case of three loops: &lt;code&gt;3dn + an + bn + cn = (3d + l)n&lt;/code&gt;, where &lt;code&gt;l = a + b + c&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the case on one loop: &lt;code&gt;dn + an + bn + cn = (d + l)n&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, the ratio between them will be &lt;code&gt;(3d + l)/(d + l)&lt;/code&gt;. When will the code with three loops be three times slower? When you spend &lt;strong&gt;zero&lt;/strong&gt; time inside the loop. And what if the operations in the loop are quite feasibly just one order of magnitude greater than loop itself? If &lt;code&gt;l = 10d&lt;/code&gt; then the ratio becomes &lt;code&gt;13/11&lt;/code&gt;, just a 18% increase, not a triple increase in time. So no, going over the collection three times doesn't make the code three times slower.&lt;/p&gt;

&lt;h3&gt;
  
  
  But you have the cost of invoking those curried functions
&lt;/h3&gt;

&lt;p&gt;Yes, but those curried functions &lt;strong&gt;&lt;em&gt;are improving the legibility of the code&lt;/em&gt;&lt;/strong&gt; clearly conveying its intention. And if you want to stop comparing apples to oranges, you can still use similar operations in the functional code just including some anonymous functions (and even replacing &lt;code&gt;reduce&lt;/code&gt; with &lt;code&gt;sum&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;totalPrice&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;sum&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Anyway, you create lot of useless structures
&lt;/h2&gt;

&lt;p&gt;Ok, here you may have a fair point. When you perform functional transformations, each transformation creates an intermediate structure which is discarded once its results are used in the next step of the functional pipeline.&lt;/p&gt;

&lt;p&gt;So in this case &lt;code&gt;map&lt;/code&gt; would create a structure of size &lt;code&gt;n&lt;/code&gt;, which is consumed by &lt;code&gt;filter&lt;/code&gt; which creates another structure of at most &lt;code&gt;n&lt;/code&gt; (because it may filter out some results), which in turn is processed by &lt;code&gt;sum&lt;/code&gt; to compute the final result. So we have an &lt;code&gt;O(2n)&lt;/code&gt; space complexity solution (just kidding, &lt;code&gt;O(n)&lt;/code&gt;, I wanted to check if you were still paying attention).&lt;/p&gt;

&lt;p&gt;In any case, unless we are doing some kind of nested transformations, we will have a solution whose space grows linearly with the size of the input, and arguably worsened by the number of steps in the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a pointless discussion: strict vs lazy evaluation
&lt;/h2&gt;

&lt;p&gt;Once reached this point I would just include a method invocation in the functional code to finally settle the question:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;totalPrice&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;products&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;view&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;sum&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do you see that &lt;code&gt;.view&lt;/code&gt; method there? That's the Scala way of switching to lazy evaluation in collections, with the use of &lt;code&gt;View&lt;/code&gt;s. In contrast to strict evaluation, where all the transformations are eagerly applied — hence producing new structures — when you use lazy evaluation, &lt;strong&gt;computations are delayed until they are strictly needed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In our case this would mean that &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;filter&lt;/code&gt;, instead of returning a concrete collection, would be returning another &lt;code&gt;View&lt;/code&gt; (wrapping the underlying view and remembering the closure needed to compute values in that new view), but without performing any actual operation.&lt;/p&gt;

&lt;p&gt;Once we reach &lt;code&gt;sum&lt;/code&gt;, which needs to access the elements to produce its output, we do the real computation. But thanks to the magic of lazy evaluation, &lt;strong&gt;we loop through the original input only once without creating any intermediate structure&lt;/strong&gt;, making this functional code effectively equivalent to the single-loop imperative code we saw in the beginning.&lt;/p&gt;

&lt;p&gt;Of course lazy evaluation comes with its costs, in this case creating the intermediate view structures, and creating and storing those intermediate closures, which for small input sizes may be even more costly than simply performing the "loop" several times and creating intermediate collections.&lt;/p&gt;

&lt;p&gt;In the case of functional languages, some of them are lazy (or non-strict) by default, as Haskell, and others are strict by default offering mechanisms to switch to lazy evaluation, as Scala.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this discussion is even more pointless: legibility and maintainability
&lt;/h2&gt;

&lt;p&gt;As I said in the beginning of this article, it seems that seniority is considered to be directly proportional to the number of features you know of a given language and inversely proportional to the length of the code you generate. It also seems to be related with your ability to micro-optimise your code, so it has &lt;code&gt;O(n)&lt;/code&gt; performance instead of &lt;code&gt;O(3n)&lt;/code&gt; performance (kidding again).&lt;/p&gt;

&lt;p&gt;I would argue that in a business environment &lt;strong&gt;the senior developer&lt;/strong&gt; (or the valuable developer) &lt;strong&gt;is that who manages to lower the total cost of ownership of a software product as much as possible&lt;/strong&gt;. Or simply said, the one who saves more company's money. But how can we know where to focus when trying to save money?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.oreilly.com/library/view/97-things-every/9780596805425/ch34.html" rel="noopener noreferrer"&gt;According to some people who know much more than me&lt;/a&gt; (I won't be the one disagreeing with an O'Reilly's published author):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fully 60% of the life cycle costs of software systems come from maintenance [...]. During maintenance, 60% of the costs on average relate to user-generated enhancements (changing requirements), [...], and 17% to bug fixes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So it seems that maintenance costs are much greater than operational costs (those related to the infrastructure needed to run the software). This means that unless you are Google — and an improvement in performance has an impact on millions of machines — you better focus on making your software much more maintainable and bug free. And that means stopping micro-optimising your code for measly performance gains and instead focus on making your code tell a beautiful story, one that other fellow developers and your future self are delighted to read and evolve. I would argue that functional programming has an edge here, but that is probably the topic for another post.&lt;/p&gt;

&lt;p&gt;Anyway I hope that you, as Martin Golding asked,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>functional</category>
      <category>scala</category>
      <category>performance</category>
      <category>maintainability</category>
    </item>
    <item>
      <title>Why Swift Sucks as a Functional Language</title>
      <dc:creator>José González</dc:creator>
      <pubDate>Sun, 08 Mar 2026 17:09:11 +0000</pubDate>
      <link>https://dev.to/jgongo/why-swift-sucks-as-a-functional-language-2ked</link>
      <guid>https://dev.to/jgongo/why-swift-sucks-as-a-functional-language-2ked</guid>
      <description>&lt;p&gt;Some months ago, I became one of the many victims of a dreaded massive layoff. Due to personal issues — among them caring for my father after he fell ill with COVID — I'm still searching for a job.&lt;/p&gt;

&lt;p&gt;In this search, I've encountered a trend that's relatively new to me: the rise of coding tests. I studied data structures and algorithms during my degree, but that was more years ago than I'm comfortable admitting. Years of just storing data in databases and retrieving it to display on a screen haven't exactly kept those skills sharp.&lt;/p&gt;

&lt;p&gt;In my last set of interviews, I failed more often than I'd like to admit. Thankfully, I came across a kind team lead (yes, they exist!) who gave me valuable feedback on my performance. With his guidance, I've developed a plan to succeed in future interviews. But that's a topic for another post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: Find any local maxima
&lt;/h2&gt;

&lt;p&gt;One of the coding problems I encountered was to find a local maxima in an array of integers. A &lt;em&gt;local maxima&lt;/em&gt; is a value greater than its neighbours, considering only one neighbour for the first and last elements. The input array had an additional constraint: you wouldn't find any repeated values.&lt;/p&gt;

&lt;p&gt;The key insight needed to properly solve this problem (which I failed to find without a bit of help due to a severe case of rustiness in some parts of my brain) is this: if you select any value which isn't a local maxima, you are guaranteed to find one on the side of a neighbour that's greater than the current value.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we pick the middle position (&lt;code&gt;6&lt;/code&gt;), this isn't a local maxima, since &lt;code&gt;6 &amp;lt; 8&lt;/code&gt; (right neighbour). This &lt;code&gt;8&lt;/code&gt; becomes a local maxima candidate (it is already halfway there after all). So if we now take the right half of the array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;rightSideValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we are guaranteed to have a local maxima. Why? &lt;code&gt;8&lt;/code&gt; was already greater than its left neighbour, so we have two cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;either &lt;code&gt;8&lt;/code&gt; is greater than its right neighbour, then &lt;code&gt;8&lt;/code&gt; is a local maxima&lt;/li&gt;
&lt;li&gt;or &lt;code&gt;8&lt;/code&gt; is less than its right neighbour, in which case we can discard it and repeat the process, potentially finding the maxima at the end of this subarray&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have a typical divide and conquer approach which will give us &lt;code&gt;O(log(n))&lt;/code&gt; complexity. With this insight in mind implementing this should be trivial... Or maybe not?&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: Swift vs Scala (or imperative vs functional)
&lt;/h2&gt;

&lt;p&gt;During the interview, I mistakenly used Swift, despite having spent months on the bench and, adding insult to injury, recently diving into Scala again. My brain works in a curious way and tends to create big pictures while forgetting details and mixing things up if I don't refresh them continuously, so I leaned toward functional programming, leading to some &lt;em&gt;interesting&lt;/em&gt; decisions that most likely confused my interviewer.&lt;/p&gt;

&lt;p&gt;A few days after the interview, I revisited and solved the problem using Scala, but just out of curiosity I asked ChatGPT about the solution in Swift to compare it with my solution. Here's the imperative Swift solution it generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;findAnyLocalMaxima&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="k"&gt;right&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;right&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

        &lt;span class="c1"&gt;// Check if mid is a local maxima&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// If left neighbor is greater, search the left half&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Otherwise, search the right half&lt;/span&gt;
            &lt;span class="k"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What was my solution in Scala like? Let's take a look at the functional alternative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;scala.annotation.tailrec&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;scala.collection.IndexedSeqView&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;findAnyLocalMaxima&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
  &lt;span class="nf"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;IndexedSeqView&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hasLocalMaximaAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
      &lt;span class="nf"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;leftHalf&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;slice&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;length&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rightHalf&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;slice&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;length&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;length&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="nd"&gt;@tailrec&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;findAnyLocalMaximaInView&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;IndexedSeqView&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="nv"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;length&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;middleIndex&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;hasLocalMaximaAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middleIndex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;then&lt;/span&gt;
          &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middleIndex&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middleIndex&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middleIndex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;then&lt;/span&gt;
          &lt;span class="nf"&gt;findAnyLocalMaximaInView&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;leftHalf&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="nf"&gt;findAnyLocalMaximaInView&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;rightHalf&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;max&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="n"&gt;findAnyLocalMaximaInView&lt;/span&gt;

  &lt;span class="nf"&gt;findAnyLocalMaximaInView&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;view&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="n"&gt;findAnyLocalMaxima&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, I admit it, I got a bit fancy with that extension to make the main code even more readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparisons are odious, so let's compare!
&lt;/h2&gt;

&lt;p&gt;Functional programming emphasises &lt;a href="https://en.wikipedia.org/wiki/Referential_transparency" rel="noopener noreferrer"&gt;&lt;em&gt;referential transparency&lt;/em&gt;&lt;/a&gt; — the ability to replace a function call with its result without changing the program's behaviour, as long as you use &lt;a href="https://en.wikipedia.org/wiki/Pure_function" rel="noopener noreferrer"&gt;&lt;em&gt;pure functions&lt;/em&gt;&lt;/a&gt;. This aligns with recursive approaches, where you rewrite the problem into smaller cases to reach a base case. As a result, functional programming favours recursive calls instead of loops, because that way you express much better the intent of your code.&lt;/p&gt;

&lt;p&gt;If you inspect the above functional code the algorithm's intent is crystal clear:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check if the middle value is a local maxima&lt;/li&gt;
&lt;li&gt;If not, recurse into the appropriate half&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That intention is totally lost in the imperative version, which relies on side effects to adjust indices to inspect the data you are interested in. You even require comments explaining the logic!&lt;/p&gt;

&lt;p&gt;So what's the problem of going fully functional in Swift?&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't look at my tail, I'm not optimising it
&lt;/h3&gt;

&lt;p&gt;In two words: &lt;strong&gt;stack overflows!&lt;/strong&gt; I bet you will have faced at some point a stack overflow exception. This happens because whenever you invoke a new function you push a &lt;a href="https://en.wikipedia.org/wiki/Call_stack#Structure" rel="noopener noreferrer"&gt;&lt;em&gt;frame&lt;/em&gt;&lt;/a&gt; in the &lt;a href="https://en.wikipedia.org/wiki/Call_stack" rel="noopener noreferrer"&gt;&lt;em&gt;stack&lt;/em&gt;&lt;/a&gt; containing information needed for the runtime environment to run that function. So if you nest too many calls, the stack runs out of space and you get the dreaded stack overflow exception.&lt;/p&gt;

&lt;p&gt;But wait a minute... Isn't this a problem in Scala too? No! Why? Because Scala &lt;strong&gt;is a proper functional language&lt;/strong&gt; in this aspect. This mechanism of recursive invocation instead of loops is so common in functional programming that all decent functional languages have what is called &lt;a href="https://en.wikipedia.org/wiki/Tail_call" rel="noopener noreferrer"&gt;&lt;em&gt;tail call&lt;/em&gt; optimisation&lt;/a&gt; (&lt;em&gt;TCO&lt;/em&gt;), where instead of adding a new frame to the stack, the current frame is substituted if the last operation of a function is a call to itself: you simply won't need the previous frame if you do nothing apart from recursively calling yourself.&lt;/p&gt;

&lt;p&gt;See that &lt;code&gt;@tailrec&lt;/code&gt; annotation before &lt;code&gt;findAnyLocalMaximaInView&lt;/code&gt;? That is in fact signalling the Scala compiler to use this optimisation so you can confidently make potentially infinite recursive calls without incurring in a stack overflow exception.&lt;/p&gt;

&lt;h3&gt;
  
  
  But wait a minute, Swift DOES optimise tail calls
&lt;/h3&gt;

&lt;p&gt;Well, Swift &lt;strong&gt;sometimes&lt;/strong&gt; optimises tail calls.&lt;/p&gt;

&lt;p&gt;First of all, depending on the level of optimisation you set during the compilation phase, tail call optimisation may be left out completely causing a stack overflow in your debug build (when you use &lt;code&gt;-Onone&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;But even if you set the optimisation level to &lt;code&gt;-Osize&lt;/code&gt; or &lt;code&gt;-O&lt;/code&gt; as far as I know there is no guarantee that tail calls will be optimised: they aren't mentioned at all in the &lt;a href="https://github.com/swiftlang/swift/blob/main/docs/OptimizationTips.rst" rel="noopener noreferrer"&gt;Writing High-Performance Swift Code&lt;/a&gt; article of the Swift documentation, and I have only been able to find an &lt;a href="https://forums.swift.org/t/proposal-tail-call-optimization-keyword-attribute/181" rel="noopener noreferrer"&gt;ancient proposal in the Swift Forums&lt;/a&gt; regarding this (where they mention Scala's &lt;code&gt;@tailrec&lt;/code&gt; by the way) that was apparently closed without being implemented.&lt;/p&gt;

&lt;p&gt;Summing up, unless I stand corrected (which I'd love), if you want to use one of the most common mechanisms of algorithm implementation in functional programming, this is tail recursion, you are at your own risk. Sometimes it will work, sometimes it won't. But hey, maybe you are one of those who like to live on the edge. And we don't have data structures requiring thousands or millions of frames in our stacks... Until we do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ok, but functional approaches are awful regarding space complexity
&lt;/h3&gt;

&lt;p&gt;Coding tests focus on both time and space complexity: does your algorithm use an awful amount of extra space to deliver its results? How does that extra space grow in relation to the input data? Is it a constant, linear, logarithmic or quadratic relationship? If the functional approach is awful regarding this, doesn't that rule out this approach in favour of the imperative mutable approach?&lt;/p&gt;

&lt;p&gt;When you think of functional programming you also usually think of immutability. You use to work with immutable data structures, transforming them in pipelines that in the end produce the desired results. All those transformations usually create intermediate data structures that are created and discarded on the fly, as steps leading you to the final goal.&lt;/p&gt;

&lt;p&gt;In the case of recursive algorithms with a divide and conquer strategy you usually pass down chunks of the original input data until you reach some base case that is trivially solved, and then the solution is built up from the bottom. In the case of this problem we are passing down approximately half the input data until we reach the base cases. If we sum all these arrays that we create &lt;em&gt;(?)&lt;/em&gt; we get that the space complexity of the functional implementation is &lt;code&gt;O(n)&lt;/code&gt;. This is, we need additional space that is linearly proportional to the size of the input data. Or maybe not?&lt;/p&gt;

&lt;p&gt;Here both Scala and Swift are smart enough to provide mechanisms to avoid this problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swift returns &lt;code&gt;Slice&lt;/code&gt;s or &lt;code&gt;ArraySlice&lt;/code&gt;s when performing operations on &lt;code&gt;Array&lt;/code&gt;s that return a chunk of the array (for example &lt;code&gt;prefix(_:)&lt;/code&gt; or &lt;code&gt;subscript(_:)&lt;/code&gt; with a &lt;code&gt;Range&lt;/code&gt;). These are views wrapping an underlying collection that don't use any additional memory to hold their elements, so they have &lt;code&gt;O(1)&lt;/code&gt; time and space complexity.&lt;/li&gt;
&lt;li&gt;Scala has a similar mechanism, although a bit more explicit. You can see it in the code when we invoke &lt;code&gt;numbers.view&lt;/code&gt;: we are creating a view which will wrap the underlying collection, so all the operations will be performed without creating additional structures. In our case we can &lt;code&gt;slice&lt;/code&gt; those arrays (well, &lt;code&gt;IndexedSeqView&lt;/code&gt;s to be fair) ad infinitum with the certainty that we will remain &lt;code&gt;O(1)&lt;/code&gt; compliant.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So yes, maybe the title was a bit of a clickbait, although this is not the only reason why I think Swift still sucks as a real functional language. Anyway if you reached this point you likely agree that the functional approach is way better at conveying the original algorithm definition and:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;either you have learnt that Swift isn't suitable to be used as a real functional language unless you want to ditch one of the most common approaches to implementing functional algorithms (tail recursion)&lt;/li&gt;
&lt;li&gt;or you know more than me regarding tail recursion optimisation in Swift, in which case I'd love to hear you in the comments!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swift</category>
      <category>scala</category>
      <category>functional</category>
      <category>tailrecursion</category>
    </item>
  </channel>
</rss>
