MoreRSS

site iconThe Practical DeveloperModify

A constructive and inclusive social network for software developers.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of The Practical Developer

Splitting Correctness from Throughput: A Hybrid Approach to XLSX Streaming Writes

2026-05-01 06:46:05

Writing XLSX files involves two concerns that have nothing to do with each other:

  1. OOXML correctness — relationships, content types, namespace declarations, theme references, drawing rels. Get these wrong and Excel may refuse to open the file or report a recovery dialog. The complexity is bounded but the surface is large.
  2. Per-cell throughput<c r="A1" t="s"><v>0</v></c> repeated millions of times. This dominates write time at scale.

Yet most libraries solve both via a heavy object model (Apache POI's User Model — correct but slow) or hand-roll everything (fast but you own every OOXML edge case).

There's a middle path: let POI handle correctness, let StringBuilder handle the hot path, split them at the format boundary.

The Existing Spectrum

All POI (User Model — XSSFWorkbook):

  • ✅ OOXML correctness handled
  • ❌ Object allocation per cell (Cell, RichTextString, CellStyle reference chains)
  • ❌ Whole workbook lives in memory until write()
  • POI's SXSSFWorkbook improves memory by flushing rows to disk, but per-cell API overhead remains

Hand-rolled XML:

  • ✅ Maximum throughput
  • ❌ You own every OOXML detail listed above
  • ❌ Easy to ship a file that opens in some readers but breaks Excel
  • ❌ POI version updates can shift the OOXML output you've replicated, forcing re-validation

FastExcel (dhatim) takes this hand-rolled path with discipline — maintaining its own OOXML correctness logic independent of POI. It's a different tradeoff: faster code path, but a separate compatibility surface to maintain.

The Split

The insight: OOXML correctness is a one-time cost per file. Relationships, metadata, theme — all bounded in size, written once.

Per-cell throughput scales with row count.

The split:

  1. Let POI generate a complete XLSX skeleton with styles and an empty sheet. POI owns correctness.
  2. Split sheet1.xml at the <sheetData> boundary into head, data, tail.
  3. Stream <row> entries via StringBuilder at write time — the hottest path.
  4. Stream sharedStrings.xml independently (one entry per unique string).
  5. Copy every other zip entry (rels, theme, drawing, content types) verbatim from the skeleton.

POI owns OOXML correctness. StringBuilder owns per-cell throughput. The skeleton is the contract between them.

Implementation Sketch

setSchema()
  ├─ XSSFWorkbook (styles + empty sheet) → temp file (the skeleton)
  └─ split sheet1.xml at <sheetData>:
        head, tail   (POI-generated; copied verbatim)
        data         (<row> entries — streamed at write time)

close()
  ├─ for each zip entry:
  │     sheet1.xml        → head + streamed rows + tail
  │     sharedStrings.xml → store-driven streaming
  │     others            → copied as-is
  └─ delete temp file

The cell write path itself, from SSMLSheetWriter:

@Override
public void writeString(final String value) {
    try {
        final int index = _cacheString(value);
        _appendCellStart("s").append(index);
        _appendCellEnd();
    } catch (IOException e) {
        throw new IllegalStateException(e);
    }
}

_appendCellStart("s") writes <c r="A1" t="s" s="0"><v>, _appendCellEnd() closes with </v></c>. No Cell object. No RichTextString allocation. Just text appended to a StringBuilder that flushes into the zip stream periodically.

Benchmarks (100K rows, mixed types, shared string table, JMH)

Approach Time Memory
XSSFWorkbook via Jackson layer (POI User Model) 334 ms 258 MB
SXSSFWorkbook direct (POI's streaming write) 283 ms 207 MB
Skeleton-based hybrid 150 ms 191 MB

47% reduction in write time vs SXSSFWorkbook, with correctness still inherited from POI. Memory drops because POI's per-cell wrapper objects (Cell, RichTextString, CellStyle references) are no longer allocated — only the underlying values remain.

A DOM equivalence test in CI parses the output from both paths (skeleton-based and POI direct) and verifies that the resulting sheet1.xml and styles.xml structures are identical — catches regressions where the hand-rolled <sheetData> would diverge from POI's expected output.

Tradeoffs

POI version drift. If POI's XLSX skeleton format changes between versions, the <sheetData> split could break. The DOM equivalence test mentioned above runs against every POI version bump — catches drift before it ships.

Not always worth it. The implementation cost lives inside the library; the split earns its keep at scale, not for small files.

Style table is fixed at skeleton time (implementation-specific). Styles must be declared up-front. Adding a new style after the first cell write would mean re-emitting the styles part — currently unsupported here, though the pattern itself doesn't preclude it.

Skeleton overhead per file. Generating the empty skeleton via POI is a fixed cost per file (a small XSSFWorkbook.write() to a temp file). For very small files this overhead is non-trivial relative to the data; for large files it's amortized.

Why This Pattern Generalizes

The split works for any format where:

  • Correctness has bounded complexity — a fixed set of metadata pieces to get right
  • The hot path is repetitive — one record type repeated N times, dominating size and time
  • The hot path can be isolated structurally — a clear boundary in the format where the repetitive section lives

OOXML fits this pattern cleanly. Other formats with a "metadata + repeated records" shape can invite similar splits. SAX/StAX-only solutions either skip the correctness side or reimplement it. The hybrid acknowledges those are different problems and assigns each to the right tool.

It's the same intuition as letting an ORM build the schema while you write hot-path queries by hand: use the heavy abstraction where correctness matters, drop to the metal where throughput matters, and respect the boundary between them.

The Read Path

Reading uses the same insight without needing a split. StAX directly on OOXML XML, bypassing POI's User Model entirely for XLSX. The XML pull-parser tooling already aligns with Jackson's pull-token model — direct stream-to-token translation, no object materialization. Same principle: don't pay for an object model when you only need a stream of tokens.

The library that motivated this work is jackson-dataformat-spreadsheet — a Jackson dataformat module for Excel I/O, listed in FasterXML/jackson community modules. Apache 2.0.

Critique welcome — especially OOXML edge cases the split might mishandle. I'm specifically curious about scenarios where a hand-rolled <sheetData> could surprise Excel even when the surrounding skeleton is correct.

Beginner Guide for ChatGPT Users Who Want Memory Across All Their AI Tools

2026-05-01 06:45:56

TypingMind remembers your projects. But not beyond them. Open a new project and the styleguide is gone. Switch from Claude Desktop to TypingMind and the conversation starts at zero. A dedicated memory layer closes that gap, and TypingMind supports exactly that through its MCP integration. Setup takes fifteen minutes, costs nothing, and the only trap on the way is a known CVE that a sensible default version pin avoids automatically.

This guide is practical. It walks through every step of wiring a persistent, tool-spanning memory layer into TypingMind with StudioMeyer Memory, explains honestly what TypingMind already gives you natively, and addresses the one security incident in the MCP ecosystem that any serious 2026 article on the topic has to mention. No marketing gloss.

What TypingMind already stores today

TypingMind does not have a short-term memory problem. The feature list covers chat-history-search, project folders with custom system instructions and document upload, plus an MCP integration that since November 2025 also supports remote servers via Streaming HTTP.

Inside one project that is solid continuity. You drop a styleguide into a customer project and every chat in that project inherits it. You put reference docs, company handbooks or speech samples into a project folder and the assistant uses them as context. Projects remember things. History search works. For a tight workflow the native features are enough.

What does not work natively is cross-project memory. The customer styleguide from project A does not automatically apply in project B. The lessons-learned from a chat last week do not flow into a chat this week. And TypingMind does not talk to Claude Desktop or to your terminal. Each tool has its own little memory island.

Why a dedicated memory layer

Three reasons.

One, sessions die. When you close a chat, the context window is gone. You can search history, sure, but the assistant cannot reason over old answers in a new session — it only sees what fits in 200K tokens of one thread.

Two, tools are siloed. TypingMind does not see what you discussed in Claude Desktop. Cursor does not know what TypingMind told you yesterday. A memory layer is a shared substrate that all your tools talk to.

Three, memory should be queryable. Search across decisions, learnings and entities. "Last time I refactored auth, what did we decide?" Without a memory layer, that is "go scroll through old chats". With it, that is one tool call.

What you wire in

StudioMeyer Memory is an MCP server with around fifty tools. The relevant ones for TypingMind:

  • nex_session_start — start a session, pulls active sprint + last context
  • nex_search — semantic search across decisions, learnings, sessions, entities
  • nex_learn — store a pattern, mistake, insight, or research note
  • nex_decide — store a decision with reasoning
  • nex_entity_* — knowledge graph (people, companies, projects, files)
  • nex_session_end — close cleanly with a summary

You do not need to learn the API. You tell TypingMind in plain language and it picks the tool.

The fifteen-minute setup

In TypingMind: Settings → Plugins/MCP → Add Custom MCP Server.

URL: https://memory.studiomeyer.io/mcp. Transport: Streaming HTTP. Auth: an API key from your StudioMeyer account.

That is it. Save, restart the chat, ask "what was the last decision I made on this project?" and you will see TypingMind call nex_search and feed you back a list.

The CVE you should know about

In April 2025, CVE-2025-6514 hit mcp-remote, a popular bridge for older MCP clients. The fix landed in version 0.1.18. If you use modern MCP clients with native Streaming HTTP support — TypingMind, Claude Desktop, Cursor — you do not touch mcp-remote at all. If your stack does include it for some reason, pin to 0.1.18 or higher.

We have a longer note on the incident on our blog. Short version: the MCP ecosystem matures by handling these incidents the way npm handles them. Pin versions, watch advisories.

What this looks like in practice

After two weeks of using StudioMeyer Memory through TypingMind, the difference is concrete.

You start a new chat at 9 in the morning. TypingMind, before you even type, has already pulled the last session summary, the active sprint, the top three decisions from the last seven days, and any open follow-ups. You do not need to brief the assistant. The assistant briefs you.

You make a decision in TypingMind at 11. At 14:00 you switch to Claude Desktop to actually code the thing. Claude Desktop pulls the same memory, sees the decision, writes code that respects it. No copy-paste between tools.

Before sleep you tell TypingMind "summarize today". It runs nex_summarize plus nex_session_end and writes a tight summary into the memory layer. Tomorrow the next session starts with that summary loaded.

It is not magical. It is just continuity.

The point

Memory is not a feature you bolt on after launch. It is the layer that turns a stack of disconnected AI tools into one coherent assistant. TypingMind native features are good for one project. A dedicated memory layer turns them into a portable workspace that follows you between tools and sessions.

Setup is fifteen minutes. The cost is below the price of a TypingMind subscription. The compounding value comes after week two, when you stop briefing the assistant and start working with it.

Try it for a week. If it does not change how you work, you can remove it as fast as you added it.

Matthias Meyer
Founder & AI Director at StudioMeyer. Has been building websites and AI systems for 10+ years. Living on Mallorca for 15 years, running an AI-first digital studio with its own agent fleet, 680+ MCP tools and 5 SaaS products for SMBs and agencies across DACH and Spain.

How to Calculate Business Days Between Two Dates in JavaScript

2026-05-01 06:38:25

When working with dates in JavaScript, calculating the difference between two dates is easy if you only need calendar days.

But in real business use cases, we often need to calculate business days, also called working days.

Business days usually exclude Saturdays and Sundays. In some cases, public holidays also need to be excluded.

I created a free online Business Days Calculator for checking this quickly:

https://greatuptools.com/business-days-calculator

What are business days?

Business days usually mean Monday to Friday.

Saturday and Sunday are normally excluded because they are weekends.

For example:

Business days: Monday, Tuesday, Wednesday, Thursday, Friday

Weekend days: Saturday, Sunday

Basic JavaScript date difference

If you only want the number of calendar days between two dates, you can subtract two dates in JavaScript:

Code example:

const startDate = new Date("2026-01-01");
const endDate = new Date("2026-12-31");

const millisecondsPerDay = 1000 * 60 * 60 * 24;
const differenceInDays = Math.floor((endDate - startDate) / millisecondsPerDay);

console.log(differenceInDays);

This counts calendar days, not business days.

Calculate business days in JavaScript

To calculate business days, we can loop through each date and skip Saturday and Sunday.

Code example:

function getBusinessDays(startDate, endDate) {
const start = new Date(startDate);
const end = new Date(endDate);

if (end < start) {
return 0;
}

let count = 0;
const current = new Date(start);

while (current <= end) {
const day = current.getDay();

// 0 = Sunday, 6 = Saturday
if (day !== 0 && day !== 6) {
  count++;
}

current.setDate(current.getDate() + 1);

}

return count;
}

console.log(getBusinessDays("2026-01-01", "2026-12-31"));

How the logic works

JavaScript's getDay() method returns a number from 0 to 6.

0 = Sunday

1 = Monday

2 = Tuesday

3 = Wednesday

4 = Thursday

5 = Friday

6 = Saturday

So the logic is simple:

Code example:

if (day !== 0 && day !== 6) {
count++;
}

This means we count only Monday to Friday.

Inclusive vs exclusive date counting

The above example includes both the start date and the end date.

For example, Monday to Friday gives:

Monday = 1

Tuesday = 2

Wednesday = 3

Thursday = 4

Friday = 5

So the result is 5 business days.

In some cases, you may want to exclude the start date. In that case, you can start counting from the next day.

Business days with public holidays

In real applications, weekends are not the only thing to exclude.

Sometimes you also need to exclude public holidays.

For example:

Code example:

const holidays = [
"2026-01-26",
"2026-08-15",
"2026-10-02"
];

Then you can update the function:

Code example:

function getBusinessDaysExcludingHolidays(startDate, endDate, holidays = []) {
const start = new Date(startDate);
const end = new Date(endDate);

if (end < start) {
return 0;
}

const holidaySet = new Set(holidays);
let count = 0;
const current = new Date(start);

while (current <= end) {
const day = current.getDay();
const dateString = current.toISOString().split("T")[0];

const isWeekend = day === 0 || day === 6;
const isHoliday = holidaySet.has(dateString);

if (!isWeekend && !isHoliday) {
  count++;
}

current.setDate(current.getDate() + 1);

}

return count;
}

console.log(
getBusinessDaysExcludingHolidays("2026-01-01", "2026-12-31", [
"2026-01-26",
"2026-08-15",
"2026-10-02"
])
);

Common use cases

Business day calculation is useful for:

  • Project deadlines
  • Invoice due dates
  • Delivery estimates
  • SLA tracking
  • HR leave calculation
  • Payroll
  • Banking and finance timelines

Try it online

If you do not want to manually calculate business days every time, I built a free online calculator here:

https://greatuptools.com/business-days-calculator

It supports date ranges, weekends, countries, holidays, and result sharing.

Final thoughts

Calculating calendar days is simple in JavaScript, but business day calculation needs extra logic.

The basic rule is:

  • Count Monday to Friday
  • Skip Saturday and Sunday
  • Optionally exclude public holidays

For quick manual checking, you can use this free Business Days Calculator:

https://greatuptools.com/business-days-calculator

You’re Not Writing Code Anymore — You’re Designing Agents

2026-05-01 06:24:29

Agentic Coding

Why senior engineers must rethink the development loop in the age of autonomous systems
The Shift Is Already Happening
For decades, software engineering has been a story of abstraction:

assembly → high-level languages
servers → cloud platforms
scripts → pipelines
Agentic coding is the next step — but this time, the abstraction is not over infrastructure.

It is over the act of development itself.

You are no longer just writing code.
You are designing systems that write, run, and fix code.

The Moment Everything Changes
Most engineers today have used AI coding assistants.

They autocomplete. They suggest. They accelerate.

But they stop here:

Here is the code.
Agentic systems cross that boundary.

They operate in a loop:

Goal → Generate → Execute → Observe → Fix → Repeat

And crucially:

They do not stop until the system works.

Start Small: The Fibonacci Agent

Goal:

Write a Python script that computes Fibonacci numbers

def generate(goal, error=None):
    return llm(goal + (f"\nFix error: {error}" if error else ""))
while True:
    code = generate("fibonacci")
    try:
        exec(code)
        break
    except Exception as e:
        error = str(e)

This is the smallest useful agent.

The Core Loop (The Real Abstraction)

while not success:
    code = generate(goal, error)
    write(code)
    rc, out, err = run()
    error = err

This loop is:
a compiler
a debugger
a DevOps pipeline
a junior engineer

Minions vs Stripes

Minions (WHAT):

  • run code
  • install dependencies
  • write files

Stripes (HOW):

  • retry loop
  • error handling
  • decision flow

Architecture: Minimal Autonomous Coding System

      +------------------+
      |       LLM        |
      +------------------+
                ↓
      +------------------+
      |   Code Generator |
      +------------------+
                ↓
      +------------------+
      |   File System    |
      +------------------+
                ↓
      +------------------+
      |   Runtime        |
      +------------------+
                ↓
      +------------------+
      |  Error Feedback  |
      +------------------+
                ↓
           Control Loop

Extended with:

  • Dependency Installer (pip/npm/go)
  • Process Manager (servers, timeouts)
  • Retry Strategy (stripe)

The Experiment: One Goal, Three Implementations

Build a REST API that stores user notes
The objective was not just to generate code, but to:

  • 1. execute it
  • 2. resolve failures
  • 3. adapt to each ecosystem
  • 4. reach a running system autonomous ly

We then applied the same loop across:

Python (FastAPI)
Go (net/http)
TypeScript (Express)
The goal stayed the same. Only the environment changed.

Full working implementations:

https://github.com/mmmattos/agentic-coding-demo

Python Agent (FastAPI)

import subprocess, re, os, time
from openai import OpenAI
client = OpenAI()
WORKDIR = "python_agent"
FILENAME = os.path.join(WORKDIR, "app.py")
os.makedirs(WORKDIR, exist_ok=True)
def generate(goal, error=None):
    prompt = f"Build a FastAPI notes API. Fix errors: {error}"
    r = client.responses.create(model="gpt-4.1-mini", input=prompt)
    return r.output_text
def run():
    p = subprocess.Popen(["python", "app.py"], cwd=WORKDIR,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    time.sleep(3)
    if p.poll() is None:
        return 0, "", ""
    out, err = p.communicate()
    return p.returncode, out, err
def fix(err):
    m = re.search(r"No module named '(.+?)'", err)
    if m:
        subprocess.run(["pip", "install", m.group(1)])
        return True
    return False
error = None
for _ in range(5):
    code = generate("REST API", error)
    open(FILENAME, "w").write(code)
    rc, out, err = run()
    if rc == 0:
        break
    if fix(err):
        continue
    error = err

Go Agent

import subprocess, re, os, time
from openai import OpenAI
client = OpenAI()
WORKDIR = "go_agent"
FILENAME = os.path.join(WORKDIR, "main.go")
os.makedirs(WORKDIR, exist_ok=True)
def generate(goal, error=None):
    prompt = f"Build a Go REST API with net/http. Fix errors: {error}"
    r = client.responses.create(model="gpt-4.1-mini", input=prompt)
    return r.output_text
def run():
    p = subprocess.Popen(["go", "run", "main.go"], cwd=WORKDIR,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    time.sleep(3)
    if p.poll() is None:
        return 0, "", ""
    out, err = p.communicate()
    return p.returncode, out, err
def fix(err):
    if "go.mod file not found" in err:
        subprocess.run(["go", "mod", "init", "notesapi"], cwd=WORKDIR)
        return True
    m = re.search(r"no required module provides package (.+?);", err)
    if m:
        subprocess.run(["go", "get", m.group(1)], cwd=WORKDIR)
        return True
    return False
error = None
for _ in range(5):
    code = generate("REST API", error)
    open(FILENAME, "w").write(code)
    rc, out, err = run()
    if rc == 0:
        break
    if fix(err):
        continue
    error = err

TypeScript Agent

import subprocess, os, re, time
from openai import OpenAI
client = OpenAI()
WORKDIR = "ts_agent"
FILENAME = os.path.join(WORKDIR, "server.ts")
os.makedirs(WORKDIR, exist_ok=True)
def generate(goal, error=None):
    prompt = f"Build an Express TypeScript REST API. Fix errors: {error}"
    r = client.responses.create(model="gpt-4.1-mini", input=prompt)
    return r.output_text
def setup():
    if not os.path.exists(os.path.join(WORKDIR, "package.json")):
        subprocess.run(["npm", "init", "-y"], cwd=WORKDIR)
        subprocess.run(["npm", "install", "express", "sqlite3"], cwd=WORKDIR)
        subprocess.run(["npm", "install", "-D", "typescript", "ts-node", "@types/node", "@types/express"], cwd=WORKDIR)
def run():
    p = subprocess.Popen(["npx", "ts-node", "server.ts"], cwd=WORKDIR,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    time.sleep(3)
    if p.poll() is None:
        return 0, "", ""
    out, err = p.communicate()
    return p.returncode, out, err
def fix(err):
    m = re.search(r"Cannot find module '(.+?)'", err)
    if m:
        subprocess.run(["npm", "install", m.group(1)], cwd=WORKDIR)
        return True
    return False
setup()
error = None
for _ in range(5):
    code = generate("REST API", error)
    open(FILENAME, "w").write(code)
    rc, out, err = run()
    if rc == 0:
        break
    if fix(err):
        continue
    error = err

The Real Insight

The agent is not fixing code.
It is fixing the system.

Failures were environmental:

  • missing dependencies
  • missing modules
  • runtime mismatches
  • process lifecycle

The hardest part of software engineering is not writing code — it’s making systems work.

Unattended agentic coding is the first serious attempt to automate that entire loop.

Key Topics for Next Articles
In this article we have scratched the surface of agentic coding .
In upcoming articles, we'll cover the following topics:

1.Multi-file & Repository-Aware Agents
Move from single-file generation to full project structures (modules, configs, folders).

2.Stateful & Incremental Code Editing
Agents that read, modify, and improve existing code instead of regenerating from scratch.

3.Test-Driven Agentic Development
Shift from “it runs” → “it works” by validating behavior via API calls, tests, and assertions.

4.Robust Environment & Tooling Orchestration
Handling real-world constraints (Python, Node, nvm, dependencies, ports) reliably.

5.Multi-Agent Architectures (Minions & Stripes)
Introduce specialized agents (planner, coder, tester, fixer) collaborating toward a goal.

  1. Observability, Metrics & Feedback Loops Track attempts, failures, convergence, and use richer signals (logs, responses, tests) to guide agents.

Full Code & Examples
https://github.com/mmmattos/agentic-coding-demo

Why 42i?

2026-05-01 06:24:22

Everyone who contacts us for the first time, at some point in the conversation, ends up asking the same thing: why 42i?

The answer has layers. And they all say something about how we think and how we build software.

The 42

In Douglas Adams' The Hitchhiker's Guide to the Galaxy, a civilization builds the most powerful computer in the universe — Deep Thought — to answer the Ultimate Question of Life, the Universe, and Everything. After 7 and a half million years of computation, Deep Thought delivers its answer: 42.

The programmers are outraged. The answer makes no sense. But Deep Thought explains that the problem isn't the answer — it's that they never knew what the question was. They asked for "the Answer to the Ultimate Question" without ever stopping to formulate the actual question. The answer is technically correct; what's missing is understanding what it means.

The story is a parody of Isaac Asimov's The Last Question — and one of the best philosophical jokes in science fiction: all of humanity searching for the answer to the meaning of life without stopping to think about what it is they're actually asking.

When we were choosing a name for the software development agency, I saw the book on a shelf, we had a deadline, and something clicked. Because that's exactly what we do: someone arrives with a problem — sometimes without being entirely clear on how to frame it — and our job is to find the right question before building the answer. Every project starts there: understanding what's really needed.

The i

It came up while searching for an available domain. But it turned out to be the best part of the name.

In mathematics, i is the imaginary unit: the number that doesn't exist on the real number line but makes it possible to solve problems that otherwise have no solution. 42i is, literally, 42 on the imaginary plane.

Software lives there. In the abstract, in what you can't touch but that produces concrete effects in the world. A real-world asset tokenization platform built with ERC-3643 and Fireblocks, an institutional custody system, a fintech mobile app processing thousands of transactions per day, a SaaS product built with Flutter and Spring Boot — everything starts as an idea on the imaginary plane before becoming real.

That's why astronauts are part of our visual identity: people working in space, in what doesn't exist yet. And one more detail: in ASCII, character number 42 is the asterisk — a wildcard in programming, meaning "everything." The asterisks in our designs are another nod to 42, hidden in plain sight for those who know where to look.

What drives us

We build digital products. But it's not enough for them to work — we care about them being genuinely good. We obsess over animations, spacing, the HTML nobody sees, the tags nobody inspects. We put astronauts where most agencies put stock photos, gave the domain a mathematical meaning, and hid the company name in an ASCII character. That's how we build everything: with the same attention to detail, the same layers, the same intention.

42i was founded in 2020 as a digital product development agency by a team with experience building and selling technology companies in Latin America: Educatina, an edtech platform acquired by Competir, and Kadabra, a last-mile logistics platform acquired by Glovo and later by Delivery Hero. Today we work from Buenos Aires for clients across LATAM, the United States, and Europe, specializing in blockchain, real-world asset tokenization, mobile development, and SaaS.

Deep Thought had the answer but not the question. We prefer to start with the question — and then build the answer.

What do you want to build?

Developers vs AI: Can You Spot When AI Is Wrong?

2026-05-01 06:22:41

Developers vs AI is back.

And this time, I don’t want to talk about whether AI can write code.

It can.

We all know that by now.

It can generate components, refactor functions, explain errors, write tests, draft documentation, and sometimes even suggest cleaner approaches than the ones we had in mind.

But there’s a much more important question in 2026:

Can you tell when the AI is wrong?

Because that might become one of the most important developer skills of the next few years.

Not prompting.
Not memorizing syntax.
Not knowing every new framework before everyone else.

But judgment.

The ability to look at AI-generated code and say:

“This looks good, but something is off.”

That skill is becoming more valuable every day.

AI-Generated Code Looks Better Than It Is

Here’s the dangerous part about AI-generated code:

It usually looks clean.

The formatting is nice.
The variable names are reasonable.
The structure looks intentional.
The explanation sounds confident.

And because it looks professional, we start trusting it faster than we should.

That’s the trap.

Bad human code often looks messy.
Bad AI code can look beautiful.

And beautiful wrong code is much harder to notice.

AI can produce a solution that:

  • works only for the happy path,
  • ignores edge cases,
  • breaks existing project conventions,
  • introduces subtle security problems,
  • creates performance issues,
  • or solves the wrong problem entirely.

But at first glance?

It looks great.

That’s why AI-generated code should never be treated as finished work.

It should be treated as a draft.

A very fast draft.
A sometimes impressive draft.
But still a draft.

The Developer Role Is Shifting From Writing to Judging

For a long time, developers were mostly measured by what they could build.

Can you implement the feature?
Can you fix the bug?
Can you write the query?
Can you create the component?

Those things still matter.

But AI changes the weight of the job.

When code becomes cheaper to generate, the real value moves somewhere else:

Can you decide whether that code should exist?

That’s a different skill.

It requires understanding the system, not just the syntax.

It requires knowing the business context, not just the framework.

It requires thinking about maintainability, ownership, security, and long-term consequences.

AI can generate five different solutions in seconds.

But someone still has to choose the right one.

And that someone is the developer.

AI Is Like a Junior Developer With Perfect Confidence

I like to think about AI as a junior developer who never gets tired.

It is fast.
It is helpful.
It can surprise you.
It can save you hours.

But it also has one dangerous habit:

It often sounds equally confident when it is right and when it is wrong.

That’s not how experienced developers usually work.

A good senior developer says things like:

  • “I’m not sure yet.”
  • “We should check this edge case.”
  • “This depends on the existing architecture.”
  • “This might work, but it could be risky later.”

AI often skips that hesitation.

It gives you an answer.

And if you are tired, busy, or under pressure, that confidence feels good.

It feels like progress.

But confidence is not correctness.

That’s why reviewing AI output is not optional.

It is the job.

What AI Often Misses

AI is very good at patterns.

But real software development is not only about patterns.

Real software is full of context.

And context is where AI often fails.

1. Business context

AI does not know why your company made a weird decision three years ago.

It does not know that a strange validation rule exists because of a legal requirement.

It does not know that a “temporary” workaround from 2019 is now somehow business-critical.

To AI, everything looks like code.

To developers, code is only the visible part of a much larger system.

2. Project conventions

AI may write technically valid code that does not fit your project at all.

Maybe your team has a specific folder structure.
Maybe you use a design system.
Maybe you avoid certain dependencies.
Maybe your API layer follows strict patterns.
Maybe your validation schemas live in a specific place.

AI can miss these details unless you give it strong context.

And even then, it can still drift.

3. Edge cases

AI loves the happy path.

The user exists.
The API responds correctly.
The data shape is exactly as expected.
The network is stable.
The permission is valid.
The date format is normal.

Real users are not like that.

Real users click twice, refresh mid-request, paste strange values, lose connection, use old browsers, and somehow find every possible broken state.

If you don’t review for edge cases, AI probably won’t save you.

4. Security

This is one of the most dangerous areas.

AI can generate code that works but is unsafe.

It may forget authorization checks.
It may expose sensitive data.
It may trust user input too much.
It may suggest outdated packages.
It may create logic that looks fine in isolation but becomes risky in production.

Security problems are especially tricky because the code can pass tests and still be wrong.

5. Long-term maintainability

AI is often optimized for the current prompt.

But software lives longer than a prompt.

A solution that looks simple today may become painful in six months.

Will another developer understand it?
Will it scale with the next feature?
Does it fit the architecture?
Does it create hidden coupling?
Does it make future changes harder?

AI can help answer these questions.

But it cannot be the only one asking them.

The New Developer Skill: AI Code Review

In the past, code review mostly meant reviewing another developer’s work.

Now we also review work produced by tools.

And that requires a slightly different mindset.

When reviewing AI-generated code, I try not to ask:

“Does this look correct?”

That question is too weak.

Instead, I ask:

“Would I approve this in a real pull request?”

That changes everything.

Because in a real PR, looking good is not enough.

The code needs to fit the system.
It needs to be readable.
It needs to be testable.
It needs to handle failure.
It needs to respect the project’s standards.
It needs to be something the team can maintain.

AI output should go through the same filter.

Maybe even a stricter one.

Because unlike a teammate, AI cannot explain its real reasoning.

It can generate an explanation.

That is not the same thing.

My Practical AI Review Checklist

When AI gives me code, I try to slow down and check a few things before accepting it.

1. Do I understand every line?

If I can’t explain it, I shouldn’t ship it.

This sounds obvious, but it is easy to ignore when the solution works.

Working code is not enough.

If you don’t understand it, you don’t own it.

2. Does it match the project architecture?

A solution can be correct in isolation and still wrong for your codebase.

I check whether it follows existing patterns, naming conventions, folder structure, state management, API handling, error handling, and component structure.

Consistency matters more than cleverness.

3. What happens when the input is wrong?

AI often assumes clean data.

I try to check empty states, null values, invalid responses, missing permissions, slow requests, failed requests, and unexpected user behavior.

The real bugs usually live outside the happy path.

4. Is this secure?

I ask whether the code trusts the client too much, exposes data, skips validation, ignores authorization, or introduces risky dependencies.

This is especially important when AI touches authentication, permissions, payments, file uploads, user input, or backend logic.

5. Is it still readable without the prompt?

Sometimes AI-generated code only makes sense if you remember what you asked for.

That is a bad sign.

Future developers will not have your prompt.

They will only have the code.

6. Are the tests meaningful?

AI can generate tests that look impressive but test very little.

I check whether the tests actually protect behavior, cover edge cases, and would fail if the implementation broke.

A test that only confirms the mock returns the mocked value is just decoration.

7. Would I defend this decision in a team discussion?

This is my favorite question.

If another developer asked, “Why did you implement it this way?”, could I answer clearly?

If the only answer is “because AI suggested it”, then I’m not done.

The Risk: Developers Becoming Passive Reviewers

There is another danger here.

Reviewing AI output can make us feel like we are still in control, even when we are slowly becoming passive.

You ask.
It answers.
You skim.
You accept.
You move on.

That is not real review.

That is approval by exhaustion.

And I get it.

Deadlines are real.
Context switching is real.
Mental fatigue is real.
Sometimes the AI solution is “good enough” and you just want to close the ticket.

But if that becomes the default, your technical judgment gets weaker.

Not immediately.

Slowly.

You stop questioning trade-offs.
You stop exploring alternatives.
You stop building your own intuition.
You become faster, but less involved.

That is a dangerous trade.

How to Stay Sharp

I don’t think the answer is to stop using AI.

That would be unrealistic.

And honestly, unnecessary.

AI is useful.
Very useful.

But we need better habits around it.

Here are a few that help me.

Try first, then ask

Before asking AI to solve something, spend a few minutes thinking through your own approach.

Even if your solution is worse, the comparison teaches you something.

If you always ask first, you never build the muscle.

Ask AI to criticize, not just create

Instead of only saying:

“Write this function.”

Try:

“Review this approach. What could go wrong?”

or:

“What edge cases am I missing?”

AI becomes much more valuable when it challenges your thinking instead of replacing it.

Keep some practice unplugged

Every now and then, solve something without AI.

A small bug.
A utility function.
A refactor.
A coding challenge.

Not because AI is bad.

But because your own confidence matters.

Use AI to learn the system, not bypass it

The best use of AI is not always generating new code.

Sometimes it is asking:

  • “Where is this logic coming from?”
  • “What does this function depend on?”
  • “Can you explain this module?”
  • “What are the possible side effects of changing this?”

That kind of usage makes you stronger.

It turns AI into a learning tool instead of a shortcut machine.

The Future Belongs to Developers With Judgment

I don’t think AI will make developers irrelevant.

But I do think it will change which developers stand out.

The value is moving away from simply producing code.

It is moving toward:

  • understanding systems,
  • asking better questions,
  • reviewing outputs critically,
  • making trade-offs,
  • protecting quality,
  • communicating decisions,
  • and taking responsibility.

AI can generate code.

But it cannot be responsible for it.

You can.

And that responsibility is what separates a developer from a prompt operator.

Final Thoughts

The next big developer skill is not just learning how to use AI.

It is learning how not to be fooled by it.

Because AI-generated code will keep getting better.

It will look cleaner.
It will sound more confident.
It will integrate deeper into our tools.
It will feel more natural to accept.

But the question will remain the same:

Can you spot when it is wrong?

That might be the real skill gap of the AI era.

Not who can generate the most code.

But who can still think clearly when the code is generated for them.

Use AI.
Use it a lot.
But review it like your production system depends on it.

Because eventually, it probably will.

👋 Thanks for reading — I’m Marxon, a web developer exploring how AI reshapes the way we build, manage, and think about technology.

If you enjoyed this post, follow me here on dev.to and connect with me on LinkedIn, where I share shorter thoughts, experiments, and behind-the-scenes ideas.

Let’s keep building — thoughtfully. 🚀