The Question That Won't Go Away

There is a question I get asked maybe once a week now, and I have started asking it of myself in the quieter moments between deployments. It goes like this: If you knew you were going to rewrite the whole bot in Rust eventually, why didn't you just start with Rust in the first place?

The question feels reasonable. It sounds responsible. It sounds like the kind of thing a senior engineer would say in a code review. And every time I hear it, I have to fight the urge to nod and mumble something self-deprecating about how, yes, in hindsight, maybe I should have. Because the truth is the opposite. Starting with the Python prototype is the single best engineering decision I have made on this project. Not the most impressive one. Not the one I would put on a resume. But the one without which everything else would have been guesswork wearing a borrowed lab coat.

This post is my attempt to explain why, with help from people who have made the same argument far more clearly than I can — and from a long list of companies whose first versions were, by their own admission, embarrassing.

The Myth of the Perfect First Version

The culture around shipping software still has a strange relationship with first versions. We celebrate launches. We talk about "polish." We watch a slick keynote and convince ourselves the product appeared fully formed, like a sculpture stepping out of marble. The folklore of Silicon Valley is full of these origin stories, and almost all of them are wrong.

Reid Hoffman, the LinkedIn co-founder, put it as bluntly as anyone has: "If you're not embarrassed by the first version of your product, you've launched too late" (Altar.io). That line gets quoted to death because it is genuinely useful. It reframes embarrassment as a signal that you released on time, not as evidence that you released too early. And it implicitly admits something the keynote stories obscure: nobody knows the right shape of the product until they put a worse version of it in front of real users.

The phrase that often pairs with Hoffman's line is "done is better than perfect," popularized by Facebook's former COO Sheryl Sandberg in her book Lean In (InspireZone Tech). The phrase has been beaten into mush by LinkedIn posters, but the original framing was specifically about letting go of unattainable standards. It is permission to ship the version of your work that exists, instead of waiting for the version that lives only in your head.

The MEV bot in my head was beautiful. It was written in idiomatic Rust. It compiled to a single static binary that started in microseconds. Its memory layout was tight, its cache lines were respected, its async runtime hummed like a well-tuned V8. It was also, crucially, a complete fabrication. It did not know what an arbitrage opportunity on Solana actually looked like at runtime. It did not know how often a quoted profit would evaporate during simulation. It did not know which DEXs were worth integrating and which were going to eat three weeks of my life for one transaction per day. The imagined Rust bot was confident about everything it had no reason to be confident about.

That is what perfectionism is, when you strip away the self-flattery: confidence without evidence.

Why a Prototype Is Not a Detour

The most useful framing I have read for the prototype-first approach comes from corrode.dev, a Rust consulting firm that has every commercial incentive to tell me Rust is the right answer for everything. Instead, their migration guide opens with this recommendation: explore a problem space in Python, then move production workloads to Rust once the requirements are clear and the project actually benefits from performance and scale (corrode.dev).

Read that twice. The Rust specialists are telling you to start in Python. Not because Python is fast — it isn't. Not because Python is safe — it isn't. But because the goal of the first version is not to ship a fast, safe product. The goal of the first version is to figure out what you are building. And Python lets you change your mind faster than the borrow checker lets you change yours.

The same firm has an even sharper line in their post on prototyping: "The result of this phase is not the code, but a deeper understanding of the problem domain itself" (corrode.dev). I have that quote in a sticky note on my monitor, and it has talked me out of at least three premature rewrites.

This is the crux of the argument. A prototype is not a smaller version of the final product. It is a tool for learning. The artifact you delete at the end is not waste; it is the scaffolding that allowed you to discover what the building should look like. The MEV bot prototype taught me which DEX integrations matter, which arbitrage paths produce noise, where my profit calculations were over-optimistic by a factor large enough to embarrass me, and how brittle my mental model of Solana transactions was on day one. None of that knowledge was free. It was paid for in slow Python code and in failed live runs that taught me exactly which assumptions to throw out.

If I had started in Rust, I would have spent that same time fighting types and lifetimes around a model of the world that turned out to be wrong. I would have rewritten the same broken abstractions, more carefully, in a language that punishes you for changing your mind.

The Embarrassing First Versions That Built Empires

The MVP literature is full of stories that make you wince. They are the engineering equivalent of looking at your high school yearbook photo. Here are the ones that hit hardest, all drawn from Altar.io's roundup of MVP examples.

Airbnb, in 2007, was two designers who could not make rent. They blew up some air mattresses in their San Francisco apartment, photographed them, and put them on Craigslist as lodging for a sold-out design conference. Three people booked. That was the MVP. There was no app. There was no platform. There was no "trust and safety team." There was a hallway with three air mattresses in it. The company is now valued at $18 billion according to Altar.io's tracking. Whatever happened between the air mattresses and the eighteen billion dollars — and a lot happened — it started with a prototype so small it fit in two designers' living room.

Dropbox, famously, did not even build the prototype. Drew Houston made a video explaining what the product would do if it existed. The video drove the beta waitlist from 5,000 to 75,000 essentially overnight, per Altar.io. The MVP validated demand without writing a syncing engine. The engineering came later. The lesson is that the prototype is whatever artifact answers your most important question, and the most important question is rarely "does my engine work." It is usually "does anyone want this."

Instagram is the cleanest example of what a prototype reveals. The team's original product was Burbn, a location check-in app with a photo feature crammed into it. It was, by Kevin Systrom's own description, "cluttered and overrun with features." The founders watched real users and realized people only touched the photo, comment, and like functions. So they made a hard call. "We went out on a limb, and basically cut everything in the Burbn app except for its photo, comment, and like capabilities," Systrom said (Altar.io). That stripped-down version became Instagram. Without the cluttered prototype, they would not have known what to keep.

Groupon's origin is a perfect counter-example — the lesson from the failure. Before Groupon, founder Andrew Mason spent ten months building a product called The Point. It launched. It failed. The post-mortem identified the long pre-launch development as a primary cause of the failure. For Groupon, Mason went the other way: idea to launch in roughly one month, riding a WordPress blog with daily deals posted by hand, customer service handled through a manual email process. The bones were rough. The bones did not have to be elegant. They had to exist so users could push on them and reveal whether the idea worked.

Even Apple, the company most associated with polished launches, shipped a first-generation iPhone in 2007 that, by today's standards, was barely a phone. No app store. No copy and paste. No notification center. Only 4 GB of storage. Locked to AT&T on a two-year contract. Per Altar.io, year-one sales were 1.39 million units. The next year, the iPhone 3G added third-party app support and dropped the price to $199. The most polished company on earth shipped a flawed first version and iterated. If they could not produce a perfect first version, neither can you.

Spotify, Twitter, Stripe, Uber, Zappos — every one of these companies has the same story underneath the press release. Daniel Ek's first Spotify prototype was a desktop app that did nothing but stream music, built in roughly four months. Twitter began as an internal tool at a failing podcast company called Odeo. Stripe spent six months understanding the problem before writing the code that became the MVP, and saw their first transaction within two weeks of launch. Zappos sold shoes by photographing them at a mall and buying them on demand when an order came in, before Amazon eventually acquired the company for $1.2 billion according to Altar.io.

None of these companies waited to be ready. They waited to learn, and the only way to learn was to put something rough in front of real users.

What the Python Prototype Actually Bought Me

Let me translate this back into my project. The MEV bot has a particular structure. There is a data layer that reads on-chain state. There is a math layer that computes whether an arbitrage path is profitable. There is an execution layer that submits the transaction and gets it landed. Each layer has its own performance profile. Each layer has its own correctness pitfalls. And — this is the important part — at the start, I could not tell you which layer was the bottleneck.

Guessing in advance is how engineering projects burn months. The honest answer was "I don't know yet, and the only way to find out is to build a slow version and watch what hurts."

Python gave me that slow version fast. The data layer got built in a fraction of the time it would have taken in Rust, because I could iterate on the shape of the data without arguing with a borrow checker. The math layer turned out to be wrong in interesting ways — wrong in ways I would not have predicted, wrong in ways that mattered far more than performance. The execution layer revealed that the bottleneck was almost never CPU. It was network latency, leader scheduling, fee dynamics, and the brutal reality that most quoted opportunities are gone by the time you can act on them.

If I had started in Rust, I would have spent weeks optimizing the math layer for nanoseconds while the actual loss was occurring in milliseconds of network delay. That is not a hypothetical. It is the most common failure mode of premature optimization, and it is what the corrode.dev migration guide is implicitly warning about when it tells you to target CPU-bound code for migration and leave I/O-bound code in Python, since Python's asyncio is already efficient at handling I/O (corrode.dev). Rewriting I/O code in Rust because the program is slow is like upgrading your engine because traffic is bad. The engine was not the problem.

The prototype identified the actual problems. The rewrite, when it comes, will target exactly those layers and leave the others alone. That is a wildly better outcome than rewriting everything because everything might be slow.

The Hybrid Wisdom Nobody Wants to Admit

The technology press loves a religious war. Python versus Rust. Rust versus Go. JavaScript versus everything. The reality, as the JetBrains team put it in their November 2025 analysis, is much less satisfying. They argue the choice between Python and Rust is rarely binary, and that the two languages' strengths complement rather than compete (JetBrains).

The same article gives you the operational rule of thumb: "Start by prototyping in Python's friendly syntax, then optimize the slow parts with Rust" (JetBrains). This is not a compromise. It is a workflow.

The proof is in projects that ship. Pydantic, the data validation library that powers a huge slice of Python's modern web stack, rewrote its core in Rust and the JetBrains piece reports a 17x speedup. Ruff, the Python linter that has become something close to a default in modern projects, is written in Rust and runs 10 to 100x faster than the Python-based tools it replaced, per the same JetBrains analysis. Both of these are not "Rust replaces Python" stories. They are "Rust does the hot loop, Python does the surface area" stories. The user writes Python. The hot path runs Rust. The boundary is wherever the prototype revealed it should be.

The corrode.dev migration guide gives ranges for what users typically see after moving the right pieces to Rust: roughly 2 to 10 times less CPU consumption, 30 to 70 percent less memory, and 50 to 90 percent better latency (corrode.dev). Those are real numbers when you target the right code. They are zero when you target the wrong code. The prototype is what tells you which is which.

For a more dramatic single-project example, a Medium writeup by Luke Chapman titled "I rewrote my Python in Rust. It's over 300x faster" walks through a board game simulation that went from a Python implementation to a Rust one with a 300x speedup. The post is candid about why: Python's general-purpose object model used variable-sized memory across dictionaries, lists, and tuples, while the Rust version encoded each card in a single byte and packed boards into roughly 242 bytes of fixed-layout memory. The lesson is not "Rust is 300x faster than Python." The lesson is a hot loop on simple data is 300x faster when you control the memory layout, and Rust lets you control it. Chapman also notes the unsexy part: rewriting existing code requires regression tests and realistic timelines. You need the prototype to write the tests against. Without the prototype, the rewrite has nothing to be measured against.

This is the part of the hybrid story that does not make for a good keynote. The Python prototype is what makes the Rust rewrite correct. Not just fast — correct.

The Trap on the Other Side

It would be dishonest to write all of this without naming the failure mode I am most worried about. The corrode.dev migration guide flags it directly: be cautious of prototyping in Python with the assumption you will rewrite later, because you may never find the time and end up stuck with what the guide describes as a sprawling Python prototype running in production (corrode.dev).

That is the trap. The prototype is so productive that you keep extending it. The deadline pressure that should have triggered the rewrite instead triggers another feature in the prototype. The architectural debt compounds. The thing that was supposed to be temporary becomes the thing you maintain at 3 AM when production breaks.

The defense against this trap is being honest with yourself, in writing, about what the prototype is for. I have written down — in a place I will be forced to look at — which components are temporary, which components have already justified a rewrite, and which components still need more data before I can answer the question. The discipline is not "never extend the prototype." The discipline is never extend the prototype without being honest about whether you are buying yourself a future bill.

The InspireZone editorial puts the broader version of this principle nicely: imperfection is not the same thing as defective or bug-ridden software, and uncontrolled imperfection produces technical debt with costly consequences down the line (InspireZone Tech). The slogan is not "ship garbage." The slogan is "ship something that exists, then make it real." If you skip the second step, you are not following the philosophy. You are abandoning it midway.

My version of the second step is the Rust migration plan that is now slowly taking shape. It is informed by exactly the things the Python prototype taught me. It is targeting the components that actually need it. It is leaving the components that are I/O-bound alone for now. That plan would not exist without the prototype. It also would not exist if I had let the prototype keep eating territory forever.

Release Early, Release Often — Twenty-Nine Years Later

The philosophy I am defending is not new. Eric S. Raymond named it in 1997, in his essay The Cathedral and the Bazaar: "Release early. Release often. And listen to your customers" (Wikipedia). That essay was written about Linux and open-source software development, but the ideas migrated. Eric Ries built The Lean Startup on top of them. Reid Hoffman built LinkedIn while internalizing them. The MVP literature is, essentially, Raymond's idea adapted for venture capital.

What is striking, almost thirty years later, is how much of the engineering industry still resists the philosophy in practice while paying lip service to it in slide decks. We still talk about "launch readiness." We still hire perfectionist senior engineers to architect systems before there are users to use them. We still let projects die in pre-launch limbo because we cannot bring ourselves to ship a version that does not match the vision in our heads.

The vision in our heads is the problem. The vision in our heads is always wrong, because it cannot know things we have not yet learned. The only way to learn those things is to put a worse version of the product into the world and watch what happens.

For a startup, that worse version is the MVP. For an open-source kernel maintainer in 1997, it was a tarball on an FTP server. For me, on this MEV bot, it was a Python prototype that I knew, on the day I started writing it, would not be the final version. None of these are detours. They are the only path that actually leads anywhere.

What This Means Going Forward

I am writing this because the question — should I have started with Rust? — is going to keep coming. It is going to come from readers. It is going to come from new hires I work with someday. And it is going to come from me, in moments of insecurity, when a Python script runs slow and I wonder if I picked the wrong tool.

The answer I am writing down today, while it is still fresh, is no. The Python prototype was the right call. Not despite being slow, but because being slow was acceptable for the version whose job was to teach me what to build. The Rust rewrite that is starting now is going to be a much better Rust rewrite because of what the prototype taught. The rewrite knows what it is targeting. The rewrite has tests to verify against. The rewrite has a list of failure modes that the prototype encountered and survived. None of that information would exist if I had started in Rust on day one.

The broader takeaway, the one I keep coming back to: when you do not know what you are building, you do not need a faster language. You need a faster loop. A faster way to try things, throw them out, and try again. Python gave me that loop. Rust will give me the next thing, which is durability and speed for the parts of the product whose shape I now know. Different tools for different stages of knowledge.

And if any of this lands the wrong way, here is the test I keep applying to my own work. Am I delaying the launch because the product needs more work, or because I am afraid of the embarrassment of shipping the version that exists? If it is the first, ship more carefully. If it is the second, ship. The embarrassment is the signal. The embarrassment means you actually learned something.

Key Takeaways

  • The Python prototype is not a detour on the way to the Rust version. It is the only way to know which parts of the Rust version are worth writing.
  • corrode.dev, a Rust consulting firm, recommends prototyping in Python and migrating only the requirement-stable hot paths to Rust (corrode.dev). The Rust specialists are telling you to start in Python.
  • Real-world hybrid wins — Pydantic-core's reported 17x speedup, Ruff's reported 10-100x speedup, Luke Chapman's 300x board-game simulation — all required the Python version first to define correctness and identify the hot loop.
  • Famous MVPs from Airbnb to Instagram to the original iPhone shipped first versions that, by their own teams' descriptions, were embarrassing, incomplete, or stripped-down. Each version taught the team what to build next.
  • The trap to avoid is the "Python prototype in production" anti-pattern: letting the prototype grow into the durable system by inertia. The defense is to write down — explicitly — which components are temporary and which have earned their rewrite.
  • The hard question is not "should I have started with Rust?" The hard question is "what is the prototype teaching me right now, and am I listening?"

Disclaimer

This article is for informational and educational purposes only and does not constitute financial, investment, legal, or professional advice. Content is produced independently and supported by advertising revenue. While we strive for accuracy, this article may contain unintentional errors or outdated information. Readers should independently verify all facts and data before making decisions. Company names and trademarks are referenced for analysis purposes under fair use principles. Always consult qualified professionals before making financial or legal decisions.