/

TypeScript vs Python

Field Notes

10 min read

TypeScript vs Python

TypeScript vs Python

TypeScript vs Python

Picking a runtime for agent tooling when both teams have opinions.

Picking a runtime for agent tooling when both teams have opinions.

Both teams have opinions and both teams are right, which is why this argument never ends on its own.

It also never ends because it is usually framed as one decision when it is two. Research code and production code have different lifespans, different reviewers, and different definitions of done. Forcing them into a single language means one of the two is being served badly, and the team that loses the argument spends the next year working around it.

Where Python wins

Python wins where the work is evaluation, data wrangling, and anything that touches a notebook. The library surface is simply deeper, the iteration loop is shorter, and every paper that matters ships reference code you can run the same afternoon it lands.

That last point is worth dwelling on. When a new retrieval technique or evaluation method appears, the reference implementation is in Python roughly always. Porting it to test an idea adds a day and introduces the possibility that your port is the thing that does not work. For exploratory work, that friction is the entire cost.

  • Evaluation harnesses and scoring pipelines.

  • Anything involving dataframes, statistics, or plotting.

  • Reproducing a technique from a paper before committing to it.

  • One off analysis that will be thrown away next week.

Where TypeScript wins

TypeScript wins where the agent tooling ships as part of a product: shared types with the front end, one runtime in production, and a deployment story the platform team already understands.

The shared type argument is stronger than it first appears. When the same interface describes what the API returns and what the component renders, an entire category of integration bug stops existing. Rename a field and the compiler finds every consumer. In a split stack, you find them in staging, or you find them in production.

The operational argument matters just as much and gets less airtime. A team already running Node in production has monitoring, deploy pipelines, secret management, and on call runbooks for it. Introducing a second runtime means duplicating all of that, and the duplicate is always the one that is out of date when something breaks at two in the morning.

  • Anything a customer touches directly.

  • Tool definitions that must stay in sync with an API contract.

  • Services that need to sit inside an existing deployment pipeline.

  • Long lived code that will be maintained by people who did not write it.

The Salesforce complication

In a Salesforce context the question acquires a third answer that neither camp likes: sometimes it should be Apex.

Anything that touches records transactionally, respects sharing rules by default, and needs to run inside the platform's governor limits belongs in Apex, regardless of what the team prefers. Reimplementing that logic outside the platform means reimplementing permission enforcement, and permission enforcement reimplemented by hand is a security incident with a long fuse.

The pattern that works is to keep the writes in Apex, keep orchestration outside it, and be strict about which is which. When a service outside the platform starts making decisions about what a user is allowed to see, the boundary has been drawn in the wrong place.

The rule we actually use

Our rule is boring and it has held up across a lot of projects.

Evaluation and research in Python. Anything that runs in front of a customer in TypeScript. Anything that writes to Salesforce records in Apex. And one schema definition shared between them, so the three halves cannot drift apart without somebody noticing.

That shared schema is the part teams skip and the part that makes the arrangement survivable. Without it you get three definitions of what a Case looks like, they disagree in small ways, and the disagreements surface as bugs that take a day each to trace.

  • Define the contract once, in a format all three can consume.

  • Generate types rather than writing them twice.

  • Fail the build when the generated types drift from the source.

  • Keep the boundary at data, not at logic.

What actually decides it

When a team asks which language to use, the useful counter question is who maintains this in eighteen months.

If the answer is the person writing it, optimise for their iteration speed and pick whatever they are fastest in. If the answer is a platform team who has never seen it, optimise for their operational familiarity instead, even at the cost of a slower build.

Most of these arguments are actually about that question wearing a language costume, and they resolve quickly once it is asked directly.

The short version

Stop trying to pick one. Python for research, TypeScript for product surfaces, Apex for anything transactional inside the org, and a single shared schema holding the three together.

The languages are not in competition. They are serving different lifespans, and pretending otherwise is how teams end up with a research prototype in production or a production framework slowing down an experiment.