❌

Reading view

Suddenly I’m a Go developer

I guess I’m a Go developer now.

My first programming language was BASIC. My seventh-grade math teacher, Mr. Schulte, taught BASIC to us on Fridays. He gave us programming assignments for homework during the week. Some of us loved it so much we stayed after school to play games and create extra programs, including our own crude, text-based adventure games. I remember hearing a story about a crazy new language called Pascal that didn’t have line numbers. How in the world would that work?

Years later I became a Pascal programmer — an ardent one. So ardent, in fact, that I would argue for hours with Visual Basic developers on CompuServe, AOL, and Usenet, telling them how Turbo Pascal, and then Delphi, was clearly a superior language. I was completely self-taught — if you count all the help that others gave me as teaching myself. I wrote shareware, became pretty competent, and eventually started helping others. 

People would tell me I should branch out and learn other languages — C++ or C#. But I wanted to be a master of one instead of a jack of all trades. Besides, we were all Windows developers and there was no better tool for writing Windows applications than Delphi. It became a point of pride for me, and I would stick up for Delphi and its company Borland no matter what. It got so bad that my friends would tease me, calling me “Baghdad Nick” after the famously optimistic Iraqi foreign minister during the Iraq War. 

Delphi by day

Looking back, I was foolish. I did become a pretty good Object Pascal programmer, but I would have benefited from learning JavaScript or a functional language. I did eventually learn TypeScript and a bit of C#. (I guess I was following Anders Hejlsberg around…) 

But if you were to ask me to this day what kind of code I write, I’d say I’m a Delphi developer. Indeed, that is my day job. I understand Delphi deeply, knowing all the nuances and insider tricks to making the language my own.

But over the course of a day or two, I’m now a Go developer, too. 

If coding all day isn’t enough, I have taken on a side project for a friend. I’ve been writing Delphi code for him for a while, but now he wants to shift gears. He has asked me to build a cross-platform application for his company written in Go and Wails Version 3. Now, I’m no Go expert. In fact, I only have a passing familiarity with the language. I can decipher what is going on, but I’m far from understanding the intricacies of what makes Go tick.

But that doesn’t really matter anymore, does it?

Claude bootcamp

I asked Claude to give me a tutorial on Go and Wails. It did a great job, quickly bringing me up to speed on the basics. (How cool is it that you can so easily turn a struct into a JSON object?) I described what I wanted, and asked for recommendations for libraries. The project uses Go for the “back end” and React on the “front end.” (I use the quotes because those “ends” are actually all built into a single binary.) 

In about an hour I came to understand the architecture and got a basic application up and running. I told Claude what I wanted, and it obliged. I described the basic data structures and objects needed, and Claude wrote them quite elegantly. I didn’t actually write a line of code, but there the project was, in all its glory. Go, TypeScript, and HTML. 

Claude managed the Git repository and even created a nice little CI/CD pipeline for me, building binaries for Mac, Linux, and Windows, all in one step.   

I went from being basically ignorant of the Go language to having a moderately sophisticated application in a day.

The thing is, I’m still not that good at Go. Not that good at all. But I’m a Go programmer nonetheless. Maybe next weekend I’ll become a Python programmer.

  •  

Rust 1.98.1 fixes miscompilation bug

Hot on the heels of the Rust 1.98.0 release, the Rust team has published Rust 1.98.1, an update that fixes a miscompilation in vtable generation.

Rust 1.98.1 was announced September 3.

A Rust issue opened August 21 (and now closed) noted that Rust 1.98 emits a zero method entry in a compiler-generated vtable, resulting in a segmentation fault and a crash. According to the Rust 1.98.1 release notes:

In Rust 1.98.0, in some circumstances, rustc would incorrectly generate a trait object vtable with a null pointer where a function pointer should be. This leads to undefined behavior in the emitted code. In some cases this may “just” cause segfaults due to the null pointer being loaded, but it is possible for it to be justification for arbitrary effects.

This bug is fixed in Rust 1.98.1. The Rust 1.98.0 release brought algebraic methods for addition, subtraction, multiplication, division, and remainder to floating-point types, allowing optimizations on these operations using algebraic properties of real number.

Users of a previous version of Rust installed via rustup can install Rust 1.98.1 by running the command rustup update stable. The Rust team asks developers who would like to help test future releases to consider using the beta (rustup default beta) and nightly (rustup default nightly) channels and to please report any bugs.

  •  

rustup 1.29.1 brings concurrency improvements

The team behind the rustup installer for the Rust programming language has released an update to the command-line utility.

Announced September 1, rustup 1.29.1 improves concurrency in two rustup operations, according to the Rustup Team. Now, when running rustup update, rustup will first check for possible updates in parallel. And when running rustup component add with multiple components, the components now will be installed concurrently.

The rustup 1.29.1 update also re-enables implicit installation, which was removed with rustup 1.28.0. However, implicit installation of the active toolchain in rustup-init and rustup invocations has been deprecated and will now produce a warning. In versions 1.27 and earlier, rustup would implicitly install the active toolchain if it was found missing whenever rustup was invoked. This behavior could not be disabled and had become a source of confusion, the Rustup Team said. Developers now can control it with the RUSTUP_AUTO_INSTALL environment variable or the rustup set auto-install command, the team explained in a blog post.

Elsewhere in rustup 1.29.1:

  • rustup doc now supports the --serve flag, which allows serving the docs over local HTTP. This should help users with containerized browser or rustup setups. 
  • Installing i686-pc-windows-* host toolchains on 64-bit Windows now requires --force-non-host. 
  • rustup-init will no longer leave unexpected files on disk after cancelled installations. 
  • A bug that could cause Windows installation to fail when using rustup-init.sh has been fixed.
  • “Target triple” has been renamed to “target tuple” across the project to reflect the new terminology.

An August 20 update to the Rust language brought algebraic methods for addition, subtraction, multiplication, division, and remainder to floating-point types, allowing optimizations on these operations using algebraic properties of real number.

  •  

MATLAB programming language sinking in popularity

MATLAB, a programming and numeric computation environment for engineers and scientists is sinking in popularity, according to the latest edition of the Tiobe index of programming language popularity.

In the Tiobe index for August 2026, published August 8, MATLAB is ranked 25th, having fallen out of the top 20 for the first time in more than 10 years. “Is this just a temporary dip, or is it the beginning of a more permanent decline? Personally, I believe MATLAB’s days are numbered,”  said Paul Jansen, CEO at Tiobe. One important reason for the decline may be MATLAB’s licensing model. “In a world where most programming languages are freely available, having to pay for a MATLAB license is becoming an increasingly significant disadvantage,” Jansen said.

Another reason for MATLAB’s decline is that the language has not evolved as much as other mainstream programming languages, according to Tiobe. “While languages such as Python, C++, Rust, and even newer languages continue to introduce modern language features and programming paradigms, MATLAB can increasingly feel like a language from another era,” Jansen said.

The Tiobe Programming Community index is an indicator of the popularity of programming languages. Ratings are based on the number of skilled engineers world-wide, courses, and third-party vendors. Popular websites including Google, Amazon, Wikipedia, Bing, and more than 20 others are used to calculate the ratings. 

The Tiobe index top 10 for August 2026:

  1. Python, 18.53%
  2. C, 11.10%
  3. C++, 8.62%
  4. Java, 8.25%
  5. C#, 4.09%
  6. JavaScript, 2.63%
  7. Visual Basic, 2.18%
  8. SQL, 1.88%
  9. R, 1.56%
  10. Rust, 1.45%

The Pypl Popularity of Language Index is an alternative measure of language popularity based on how often languages are searched on Google.

The Pypl index top 10 for August 2026:

  1. Python, 50.00%
  2. Java, 12.8%
  3. C/C++, 8.57%
  4. R, 4.18%
  5. JavaScript, 3.52%
  6. Objective-C, 3.01%
  7. PHP, 2.06%
  8. Rust, 1.86%
  9. C#, 1.74%
  10. Swift, 1.68%

  •  

The best new features in Python 3.15

Python 3.15 is one of the most feature-packed Python releases in many a moon, and the first release candidate has just arrived. Here’s a rundown of the biggest, boldest, and most important innovations, changes, and fixes.

Lazy imports

A long-asked for feature, lazy imports allow imports to be processed only when they’re actually used by the program. For slow-importing modules that impose a large cost on a program’s startup time, you can defer that cost to when the code of that module will actually be executed.

You can use lazy imports explicitly using the new lazy import syntax, but you can also force code with conventional imports to behave lazily, either programmatically or by using an environment variable. This makes it easy to make existing code take advantage of this feature without tons of rewriting. Best of all, there’s no drawback to making imports lazy: they otherwise behave exactly as intended.

The frozendict built-in type

Only rarely does Python add a new data type, but this is a long-debated and long-desired addition: the frozen dictionary. The frozendict behaves like a regular dictionary, except that it’s immutable (you can’t add, remove, or change elements) and it’s hashable (so you can use it as a key in another dictionary, for instance).

The sentinel() built-in type

Another new addition to the language is intended to replace a common and problematic Python pattern: creating a unique sentinel object (as an alternative to None where None could be a valid value, for example) by using object(). The new syntax ,sentinel("NAME"), creates unique objects that compare only to themselves via the is operator. These objects can be type-checked properly, and they have an informative representation instead of just a random object descriptor.

Tachyon, a statistical sampling profiler

The long-standing cProfile module profiles Python code deterministically—that is, it tracks and records every single call. That makes it precise, but it also means a cProfile-tracked program runs far slower than normal. A new profiling module in Python 3.15, profiling.sampling, uses statistical sampling methods to garner useful information about performance at a fraction of the impact on the program’s speed. The existing cProfile profiler is still available—it’s not going away—but has a new alternate name, profiling.tracing.

An upgraded JIT

CPython’s built-in just-in-time (JIT) compiler debuted in Python 3.13. Its long-term goals are to make Python programs run faster without any changes to code, in something of the same way the alternate Python runtime PyPy can speed things up. And it comes without the cost of changing to a totally different interpreter with some of its own limitations.

The first couple of revisions of the JIT didn’t promise, or deliver, a great deal of additional speed, as they were more about laying a foundation for future improvements. With Python 3.15, though, the JIT is now showing an 8% to 13% geometric mean performance improvement over standard CPython, depending on the platform and workload. The biggest changes include a new tracing front end (to enable more speedups on more kinds of code), the use of register allocation for faster and more memory-efficient work, better machine code generated by the JIT, and additional optimizations such as eliminating reference counts for some classes of objects.

It’s worth experimenting with enabling the JIT for workloads to see if there’s any measurable difference. But note that further development on the JIT is now subject to some firmly laid-out guidelines regarding the performance improvement it needs to provide before it can be considered a fully supported part of Python, and not just an experimental sideline.

Better error messages

Error messages in Python have been made more precise, detailed, and useful over the last couple of versions, and Python 3.15 continues that work. The highlights:

  • Suggestions for missing names (“x has no attribute ‘y‘. Did you mean ‘xyz‘?”) now include suggestions from the members of a given object, and not just the object itself.
  • Suggestions now also cover checks for deleting attributes, not just accessing them.
  • If the interpreter can’t come up with a suggestion for a method based on fuzzy name matching via Levenshtein distance, it consults a list of names commonly used in other languages for such methods. For example, if you attempt to use list.push() (a JavaScript method), the interpreter suggests .append(), the proper method for Python lists.

Type system improvements

The TypedDict class, which lets you create dictionaries with predefined keys and type-hinted keys and values, adds support for two new arguments in its definition. The closed argument lets you specify if only the keys specified can be used at runtime. The extra_items argument lets you specify additional keys at runtime, but only keys with a value of a specified type.

The TypeForm type definition lets you represent the value that results from evaluating a type expression. With this, type annotations can be used in places where the type itself is being used as a value—for instance, variations on operations like typing.cast or even isinstance, or as part of how a third-party type-checking tool works.

Unpacking in comprehensions

This is another long-requested feature. If you wanted to completely unpack or “flatten” a nested object using a comprehension, you used to need a function like itertools.chain() or you would have to write a nested comprehension with an ugly syntax:

x = [[1,2,3],[4,5],[6]]
y = [a for b in x for a in b]
>>> [1, 2, 3, 4, 5, 6] # y

Unpacking in comprehensions using the star operator lets you save yourself a step:

x = [[1,2,3],[4,5],[6]]
y = [*a for a in x]
>>> [1, 2, 3, 4, 5, 6] # y

Unpacking with ** also works, for instance as a way to flatten and combine dictionaries:

dicts = [{'a': 1}, {'b': 2}, {'a': 3}]
y = {**d for d in dicts}
>>> {'a': 3, 'b': 2}

Finally, this kind of unpacking can also be used to form generator expressions:

(*x for x in ["ab","cd","ef"])

The expression above creates a generator that yields:

['a', 'b', 'c', 'd', 'e', 'f']

Reverting the incremental garbage collector

Finally, Python 3.15 takes an important U-turn. Python 3.14 featured a major change to its garbage collection system—an incremental garbage collector intended to reduce the amount of program-stopping time needed to collect garbage. Unfortunately, many users reported the new garbage collector increases process memory usage, sometimes dramatically. Python 3.15 will revert back to the older generational garbage collector used in Python 3.13 and before. The incremental collector may return in a future version, but not without additional work done on it to keep this problem from resurfacing.

Other changes

  • A new math module, math.integer, gives you functions for integer-specific math such as greatest common divisor or integer square root.
  • The Stable ABI was created to make it easier to write CPython extensions that didn’t have to be recompiled to be compatible with multiple point revisions of Python. With Python 3.15, developers writing extensions that use the free-threaded (aka “no-GIL”) build of Python can now use the Stable ABI. This will require some rewriting of extensions that use the Stable ABI; it isn’t something you can accomplish simply by recompiling code with different headers.
  • CPython builds now have frame pointers enabled by default where supported. This makes it easier and faster for the stack to unwind, so that CPython can be analyzed more reliably by system-level profiling and debugging tools.
  • UTF-8 is now the default encoding for Python globally. You no longer need to specify UTF-8 as an encoding when, for instance, reading text from files. You can disable this behavior using a command-line flag or environment variable.

  •  

Rust preps improved borrow checker for stabilization

The Rust team has announced that it is enabling the Polonius Alpha borrow checker on nightly releases for testing. The team expects to fully stabilize Polonius Alpha later in the year.

The announcement was made in a blog post on August 4. In the blog post, Rust team member Jack Huey said there are no known remaining issues with Polonius Alpha and performance is generally acceptable for stabilization.

Enabling the Polonius Alpha borrow checker on nightly for testing is being done to help find serious performance regressions, unsoundness in the formulation, and diagnostic issues that need to be improved, Huey said. Issues can be reported in GitHub or Zulip.

Developers can disable Polonius Alpha and use the stable NLL borrow checker by passing -Zpolonius=off to rustc , using RUSTFLAGS=-Zpolonius=off, or including the following in a project’s .cargo/config.toml configuration file:

[target.x86_64-unknown-linux-gnu]
rustflags = ["-Zpolonius=off"]

The Polonius borrow checker has been in the works since 2018. In 2023, a new formulation of a the Polonius borrow checker was proposed that required a minimal re-architecture of the existing NLL (non-lexical lifetime) implementation and could be extended to allow more sound code to compile, according to Huey. Stabilization of that implementation has been delayed until now.

The borrow checker is a component of the Rust compiler that enforces strict rules on references, or borrowing. These include ensuring that all variables are initialized before they are used, that the same value isn’t moved twice, that a value isn’t moved while it is borrowed, that a place isn’t accessed while it is mutably borrowed (except through the reference), and that a place isn’t mutated while it is immutably borrowed, according to the Rust documentation.

  •  

Java 28 starts to take shape

Java Development Kit (JDK) 28, a non-LTS (Long-Term Support) or “feature release” of standard Java due in March 2027, has started to take shape. Features listed for JDK 28 now include a preview of value objects, switching the default mode of the Shenandoah garbage collector to generational mode, and a preview of strict field initialization in the Java Virtual Machine (JVM).

As a non-LTS release, JDK 28 will be backed by six months of support by Oracle.

The three features currently targeted to JDK 28 include the following:

  • Introduce value objects, which are immutable and lack object identity. Value objects are distinguished by the values of their fields, and can be represented by the JVM in ways that improve performance. A goal of the feature is to give developers a programming model for immutable data in which the == operator, and all other operations, distinguish objects by the values of their fields rather than their identities. Value objects is a preview language and VM feature.
  • Switch the default mode of the Shenandoah Garbage Collector (GC) to the generational mode and deprecate the non-generational mode, with the intent to remove it in a future release. Goals include signaling the intent that future development will focus on generational mode, and reducing the maintenance cost of supporting two different modes. However, it is not a goal to remove non-generational mode at this time.
  • Introduce strictly-initialized fields in the JVM. Such fields must be initialized before they are read, thus default values such as 0 or null are never observed. For strictly-initialized fields that are final, the same value is always observed. A goal is offering designers of JVM-based programming languages a model for field initialization, which has stronger integrity guarantees than the present model. This is a preview VM feature.

The predecessor to JDK 28, JDK 27, is due September 15. JDK 27 also is a non-LTS release that will be backed by six months of support by Oracle.

  •  

JetBrains open sources KotlinLLM runtime code generator

KotlinLLM, a research prototype for delegating runtime logic to a large language model (LLM) from Kotlin code, is now going open source and public, JetBrains announced.

Revealed July 28, KotlinLLM is an IntelliJ IDEA plugin prototype for experimenting with LLM-driven “Smart macros” in Kotlin, enabling code generation, runtime updates, and hot-reloading. In software engineering, LLMs are commonly used during development for code completion, code generation, and program comprehension, JetBrains noted, but using an LLM at run time of a compiled application is much less common. The existing options for doing this have the following trade-offs, according to JetBrains:

  • Direct runtime delegation is slow, non-deterministic, and costly, and makes the application depend on an LLM service at run time.
  • External agent workflows keep the generated logic outside the codebase, where it is harder to review, test, and ship.
  • Most prior work (byLLM, nightjar, Healer) targets interpreted languages like Python, not a compiled, statically typed language like Kotlin.

KotlinLLM addresses these limitations in three ways, JetBrains said:

  • The call site shows that a feature is LLM-backed, so it is visible in code review.
  • Generated behavior is saved as an ordinary Kotlin source, not kept only in the runtime session. It can be committed, reviewed, tested, and distributed like any other code.
  • Once generated, the code runs as plain Kotlin without the plugin. For scenarios that are already covered, there is no further LLM call, so no added latency or cost, and the result is reproducible.

KotlinLLM is open source under Apache License 2.0. The repository contains the KotlinLLM IntelliJ IDEA plugin, the Smart macro API, and example projects.

  •  

Microsoft updates MCP C# SDK for stateless MCP

Microsoft has released MCP C# SDK v2.0, a major update of the company’s official C# SDK for Model Context Protocol (MCP) servers and clients.

Unveiled July 28, MCP C# SDK v2.0 implements the 2026-07-28 revision of the MCP specification, which Microsoft described as the protocol’s largest revision since its launch. The 2026-07-28 revision makes the MCP protocol stateless by default, standardizes the HTTP surface to allow ordinary HTTP infrastructure to route MCP traffic, and introduces Multi Round-Trip Requests (MRTR) to support interaction without requiring long-lived sessions.

MCP C# SDK 2.0 moves in lockstep with these changes, Microsoft said. The HTTP server transport now runs statelessly by default. Whereas version 1.x of the SDK configured stateful sessions out of the box, in version 2.0 HttpServerTransportOptions.Stateless defaults to true.

MCP C# SDK v2.0 also implements a small set of HTTP headers that the 2026-07-28 MCP revision standardizes. A tools/call now includes Mcp-Method: tools/call and Mcp-Name: get_order_status alongside the JSON-RPC body. With the 2026-07-28 revision, an MCP request becomes a single, self-describing HTTP POST. This means that ordinary HTTP infrastructure such as load balancers, proxies, gateways, and web application firewalls now can act on MCP traffic without deep packet inspection, Microsoft said.

And MCP C# SDK 2.0 adds support for MRTR on the server and on the client. MRTR addresses scenarios in which tools can’t answer in a single round trip, such as when a server wants to confirm with a user before taking a significant action. In previous versions of MCP, these interactions worked only over a live, stateful session. With MRTR, they work with no session at all, because all the continuity travels in the payload, Microsoft said.

With the 2026-07-28 spec compliance landing in MCP C# SDK 2.0, Microsoft said the SDK’s next focus is end-to-end authentication and authorization. The goal is to make secure MCP deployments as “turnkey” as the rest of the stack, building on the closer OAuth and OpenID Connect alignment the spec introduced, Microsoft said. 

  •  

JDK 27: The new features of Java 27

Java Development Kit 27, an Oracle-driven, planned update to standard Java, is set to reach its initial release candidate (RC) stage on August 6. This update has features ranging from making the Garbage-First (G1) garbage collector the default collector and adding structured concurrency to simplify concurrent programming. JDK 27 is currently in rampdown phase 2, with the feature set now frozen.

Overall, there are nine features listed on what is described as a reference implementation of JDK 27. The first five features include making G1 the default garbage collector, post-quantum hybrid key exchange for TLS 1.3, lazy constants, structured concurrency, and primitive types in patterns, instanceof, and switch. The next four include compact object headers, JFR (JDK Flight Recorder) in-process data redaction, a vector API, and PEM (Privacy-Enhanced Mail) encodings for cryptographic objects.

The final release candidate stage for JDK 27 follows on August 20, with general availability scheduled for September 15. Early-access builds for JDK 27 can be found at jdk.java.net.

With JDK 27, G1 becomes the default garbage collector in all environments, rather than just in server environments. Goals are to ensure that the HotSpot JVM will always select G1 and that performance including throughput, latency, memory footprint, and startup time does not degrade significantly.

With post-quantum hybrid key exchange for TLS 1.3, the goal is to enhance the security of Java applications that require secure network communication by implementing hybrid key exchange algorithms. Such algorithms defend against future quantum computing attacks by combining a quantum-resistant algorithm with a traditional algorithm.

Lazy constants introduces an API for objects that hold unmodifiable data. Lazy constants are treated as true constants by the JVM, enabling the same performance optimizations that are enabled by declaring a field final. Compared to final fields, however, lazy constants offer greater flexibility in the timing of their initialization. This feature is in a third preview.

The goal of primitive types in patterns, instanceof, and switch is to enhance pattern matching by allowing primitive types in all pattern contexts, and to extend instanceof and switch to work with all primitive types. This feature is in a fifth preview.

Structured concurrency simplifies concurrent programming by introducing an API that treats groups of related tasks running in different threads as single units of work. Goals include streamlining error handling and cancellation, improving reliability, and enhancing observability. Structured concurrency is in its seventh preview.

JDK 27 makes compact object headers the default object header layout in the HotSpot JVM. Compact object headers reduce object headers from 96 bits down to 64 bits on 64-bit architectures, reducing heap size, improving deployment density, and increasing data locality.

JFR data in-process redaction allows users to redact command-line arguments and the initial values of environment variables and system properties in JFR recordings. JFR recording files may contain events that include sensitive data, such as secrets in command-line arguments, access tokens in environment variables, and passwords in system properties. This data is (optionally) redacted before it leaves the process, so that sensitive information does not leak.

The vector API introduces an API to express vector computations that reliably compile at run time to optimal vector instructions on supported CPUs, thus achieving better performance than equivalent scalar computations. This is an incubating API in its 12th incubation.

PEM encodings of cryptographic objects introduces an API for encoding objects that represent cryptographic keys, certificates, and certificate revocation lists in the PEM transport format, and for decoding from PEM back into objects. This is a preview API.

JDK 27 is a short-term feature release (non-LTS) that will receive six months of support from Oracle. Its predecessor, JDK 26, also a short-term release, became generally available on March 17.

  •  

Soothe your Python frustrations the smart way

For a programming language that is prized for being convenient and easy, Python has some surprising barbs and sharp edges. Find out how a new pip flag can make it easier to work with project dependencies apart from the project itself. Get the skinny on why standalone Python programs are hard to assemble, and what you can do about it. See how Python’s new Installation Manager for Microsoft Windows lets multiple versions coexist on the same system. And find out what will be shipped when Python 3.15 debuts later this year.

Top picks for Python readers on InfoWorld

New pip flag fixes longstanding Python frustration
A longstanding gripe with Python’s pip package manager has a happy ending. The next version of pip will let you install a package’s requirements without installing the package itself.

Why it’s so hard to create stand-alone Python apps
Python’s best features, its dynamism and flexibility, have a downside: They make it tough to package Python apps as standalone programs. Not impossible, though; just … complicated.

The best new features in Python 3.15
If you haven’t grabbed the beta release of Python 3.15 yet, it’s ready when you are. Here’s a rundown of all the best bits you can start using right away.

Get started with the new Python Installation Manager
This all-in-one Python version management tool is now the recommended, default way to set up and manage Python versions on Microsoft Windows — including betas, release versions, and the special JIT and free-threaded builds, too.

More good reads and Python updates elsewhere

Ruff linter gets a massive upgrade in version 0.16
The speedy, Rust-powered linter and formatter for Python now supports over 400 rules by default, making for a more useful, zero-configuration, out-of-the-box linting experience.

What every Python developer should know about the CPython ABI
A massive, multilayered unpacking of that most intimidating of Python internals, the CPython application binary interface. Even if you never plan to write a CPython extension or contribute to the CPython project, the details laid out here are eye-opening.

Free-threaded Python: past, present, and future
At PyCon 2026, CPython core developer Thomas Wouters gave a talk about how Python came to support threads via the GIL, and how its developers went about removing the GIL over many years and multiple attempts. Excellent recap available at LWN.net.

PEP 837: Extensible JSON serialization
A newly introduced Python Enhancement Proposal (intended for Python 3.16) to make Python’s native JSON serialization more extensible, for instance to allow new types to automatically support serialization via a __json__ method.

  •  

The Microsoft agent framework wars are over. The real architecture decision starts now

Over the past year, I had the same conversation with almost every team starting an AI initiative. Should we build on Semantic Kernel, AutoGen or Foundry?

At first it felt like the most important architectural decision we’d make. Each framework had its own philosophy, each promised to be the foundation for enterprise AI, and picking the wrong one felt like an expensive mistake. I spent a lot of time helping teams weigh the trade-offs.

Looking back, I think we were asking the wrong question. I certainly was.

I watched teams spend months debating SDKs while the decisions that actually decided whether their applications survived production went unexamined. Some built elaborate orchestration layers for workflows that a few deterministic functions would have handled. Others avoided agent frameworks entirely and later found they’d designed themselves into a corner.

Then Microsoft settled it for us. It introduced the unified Agent Framework, quietly moved Semantic Kernel and AutoGen into maintenance mode, and the debate I’d spent months refereeing was suddenly over. Turns out the answer to “which of the three” was “none of the three, here’s a fourth.” The framework hit version 1.0 and general availability in April 2026, stable across .NET and Python.

What surprised me wasn’t the decision. It was how fast a debate that had eaten so much of our attention stopped mattering. Microsoft changed the menu.

It didn’t change the meal.

The framework was never the hard part

Framework selection dominated almost every early conversation I had about enterprise agents. Which SDK do we standardize on? Which orchestration model gives us the most flexibility? Which one is Microsoft actually betting on?

Fair questions. But after a year of watching these projects play out, I’ve slowly come around to a different view. Those weren’t the questions that decided anything.

The first question I ask now is much smaller. Does this thing actually need an agent?

It sounds obvious, and I still get it wrong sometimes. But it’s the mistake I see most. On one project, a team spent weeks designing a multi-agent workflow for a process that ran the same four steps every time: read a document, validate it, call an API, send a notification. The diagrams looked great. The system in production didn’t. A few well-tested functions would have been easier to build, easier to maintain and a lot easier to trust.

Part of this is just that “agent” has become the word everyone reaches for. Sometimes it’s the right call. Sometimes it’s a workflow we already knew how to build, wearing a newer label. An agent earns its complexity when it genuinely has to decide things you can’t predetermine, choosing between tools, adapting to what it finds, working out its own next step. If you already know every step, you have a workflow, and a workflow is usually the better engineering choice. The consolidation didn’t change that. It just made it easier to see.

What building production agents actually taught me

Once I stopped fixating on frameworks, the same three problems kept showing up. None of them had anything to do with the SDK.

Context beats model choice

Early on I spent a lot of time comparing models, the way you’d agonize over a restaurant menu and then order what you always order. Now I spend most of it thinking about context, which is far less fun and far more useful.

I’ve watched good models fail because they were handed too much, not too little. One team I worked with gave the model access to nearly every internal document they had on the theory that more information meant better answers. It went the other way. Responses got slower, less consistent and sometimes skipped right past the thing that actually mattered. When we cut the context down to only what the task needed, the quality jumped almost immediately. I didn’t predict that. It taught me to be suspicious of “just give it everything.”

The best agent systems I’ve worked on weren’t the ones with the biggest context windows. They were the ones careful about what reached the model, and when. That’s not something the framework hands you.

Failure is where the real work is

Most agent demos look great because they’re built around the happy path. Production doesn’t extend that courtesy.

I remember a project where everything held up in testing. Then a downstream API timed out after the agent had already completed several earlier steps. We couldn’t just restart, because part of the business process had already gone through. We ended up spending far more time on recovery logic than we ever spent on prompts. That project changed how I think about this work. The hard part was never getting the model to make a decision. It was making sure the system didn’t fall apart when reality refused to follow the script.

Tool calls fail partway through. APIs return inconsistent data. Models call the same tool over and over because the last answer wasn’t what they wanted. That’s not the exception; that’s a normal Tuesday. Whether you retry, roll back, pause for a human or push on with partial results is a judgment call, and no framework is going to make it for you.

Identity is the real security boundary

This one surprised me most. The moment an agent stops being a chatbot and starts touching real business systems, identity matters more than orchestration.

Every project gets to the same question eventually. Who is this agent actually acting as? The developer’s credentials? A service account? The user who asked? Get it wrong and you’ve built something autonomous running with more access than any single person should have, which is exactly the kind of thing that looks fine until an audit. The Agent Framework, like most modern tooling, makes it easier to wire agents to tools through standards like the Model Context Protocol. That helps. But where human approval belongs, what needs extra authorization, how much rope to give the thing, those are still yours to decide.

The surprises weren’t technical

Here’s what I didn’t see coming. The hardest part of last year wasn’t technical at all. It was organizational. The moment a team heard “agent,” expectations shifted under everyone’s feet. Business stakeholders started expecting full autonomy. Developers assumed the thing could reason its way through anything. People started designing for flexibility before we’d even agreed on what problem we were solving. The word did damage before any code did. I found myself spending as much time resetting expectations as I did discussing architecture.

Build for change, not for today’s winner

I don’t think the teams that struggled last year picked the wrong framework. Semantic Kernel was reasonable. AutoGen was reasonable. Foundry made sense for plenty of cases. I’d have signed off on any of them.

The ones that got hurt put all their eggs in one framework, treating it as the foundation of the whole system instead of as one more dependency. Microsoft provided a migration path. But teams that had tightly coupled their applications to framework-specific abstractions discovered that migrating and rewriting are not the same thing. That wasn’t Microsoft’s doing. It was their own architecture’s. The teams that moved easily had kept their business logic, prompts and orchestration loose enough to evolve independently of any one SDK. For them, the change was a manageable project, not a teardown.

For what it’s worth, nobody I work with is treating this as an emergency. Most are moving the smaller workloads first, watching how they behave and leaving the production-critical systems alone until they actually understand the new abstractions. That’s the right instinct. And I doubt this is the last consolidation we’ll see, the ecosystem is still young, frameworks will keep absorbing each other and over time the differences between them will be operational more than architectural.

I don’t regret the framework debates, honestly. They were reasonable at the time. What changed wasn’t Microsoft’s roadmap.

It was mine. Watching these systems run in production taught me that the framework is the easiest piece to swap out. Recovery logic, context management, security boundaries, the business workflow itself, those stay with you long after today’s SDK gets replaced by tomorrow’s.

So, Microsoft made one decision easier by turning three frameworks into one. Good. Five years from now we’ll be on different tools, and we’ll still be asking the same handful of questions.

Does this actually need an agent? Does it have the right context? Can it recover when something breaks, because something will? Is it acting as the right person?

Those questions outlast every rewrite. That’s where I’ve learned to put my effort.

Frameworks come and go. Good architecture has to survive all of them.

This article is published as part of the Foundry Expert Contributor Network.
Want to join?

  •  

How to execute queries in parallel using EF Core

EF Core is Microsoft’s flagship ORM (object-relational mapper), the software layer that allows .NET developers to work with relational databases. The DbContext class is the core component of the EF Core framework for managing database operations. However, the DbContext class in EF Core is not thread-safe. Hence, if you share DbContext instances between multiple threads, you will often encounter data corruption issues and the InvalidOperationException.

In this article, we’ll learn how we can execute queries in parallel in EF Core by handling thread-safety issues to avoid concurrency errors. To work with the code examples provided in this article, you should have Visual Studio 2026 installed in your system. You can download Visual Studio 2026 here.

Executing EF Core queries in parallel – the problem

When working in today’s data-driven applications, you will often need to fetch data from multiple unrelated datasets. In applications that use concurrency, thread-safety is critical to guaranteeing correct execution, avoiding data corruption and race conditions, and ensuring data consistency. Let’s understand this with an example.

Let’s say we want to populate a dashboard that displays all recently processed orders, metrics, logs, and traces, as well as your application’s performance metadata. We might write the following code.

public class Dashboard
{
    public List Orders { get; set; } = new();
    public Metrics Metrics { get; set; } = new();
    public List Logs { get; set; } = new();
    public List Traces { get; set; } = new();
}
public static async Task LoadDashboardAsync(ProductService productService)
{
    Task<List>    ordersTask  = productService.GetProcessedOrdersAsync();
    Task        metricsTask = productService.GetMetricsAsync();
    Task<List> logsTask    = productService.GetRecentLogsAsync();
    Task<List>    tracesTask  = productService.GetTracesAsync();
    await Task.WhenAll(ordersTask, metricsTask, logsTask, tracesTask);
    return new Dashboard
    {
        Orders  = await ordersTask,
        Metrics = await metricsTask,
        Logs    = await logsTask,
        Traces  = await tracesTask
    };
}

In the preceding code snippet, there are four read operations that are executed by four different Task instances. Our objective is to ensure that the database round trips run in parallel instead of in sequence. We can accomplish this by usingTask.WhenAll, which starts the four tasks, waits for every task to finish, then returns the data wrapped inside a new Dashboard instance.

If we executed these queries sequentially, the user would have to wait until each query completed its execution in turn—for a total wait time equal to the sum of the times for all four queries. However, by running these queries in parallel, we reduce the wait time considerably. The user will need to wait only as long as it takes for the slowest of the four queries to complete its execution.

However, there is a danger with the above approach. If you run multiple operations on the same DbContext instance, you will see an InvalidOperationException with the following message:

A second operation started in this context before the previous operation was completed. This is usually caused by multiple threads using the same DbContext instance; instance members are not guaranteed to be thread-safe.

Databases such as SQL Server, PostgreSQL, and Oracle Database follow a request-response communication model at the connection level: a single connection can process only one command at a time. Hence, you cannot run multiple queries concurrently using the connection. If you await several operations using the same connection, EF Core detects the overlapping use of a non-thread-safe context and throws an InvalidOperationException. To run queries in parallel, you must give each task its own connection or context.

Why DbContext isn’t thread-safe – and how to work around it

The DbContext class in EF Core is designed to manage a single unit of work. To be more precise, EF Core does not provide support for running multiple operations on the same DbContext instance. This design approach creates inherent challenges when you use the same DbContext instance across multiple threads. If DbContext were thread-safe, extensive locking would be required, which would degrade data access performance.

This stateful design of DbContext makes it unsuitable for concurrent access patterns that involve loading, modifying, or tracking different sets of data simultaneously, because it needs to maintain the internal representation of database state.

The change tracker is one of the most important components of DbContext in EF Core. It monitors all entities loaded into memory and detects any changes made to them after they have been loaded. It keeps track of the original, current, and changed values of the entities, thereby enabling the EF Core runtime to know the current state of these entities when you call the SaveChanges() method on the DbContext instance.

To implement thread-safety when working with DbContext, we must write our code to ensure that each concurrent operation gets its own copy of a short-lived instance. Now, we could accomplish this by wrapping a shared DbContext instance inside a thread-safe block using the lock keyword, so that all calls to the database take place using one and only one thread at a time. This approach is illustrated in the code snippet below.

using Microsoft.EntityFrameworkCore;
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions options) : base(options) { }
    public DbSet Products => Set();
}

However, while the above approach gives us the thread-safety we need, it can degrade data access performance considerably. A better approach is to use IDbContextFactory , which creates fresh DbContext instances on demand. Calling its CreateDbContext() method is cheap and produces a fresh, isolated context every time.

The following code snippet shows how you can register an instance of type IDbContextFactory as a singleton. You can safely call this code from any thread.

builder.Services.AddDbContextFactory(options =>
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("Default")));

Executing EF Core queries in parallel – the solution

Now let’s see how we can put IDbContextFactory to work. The following code illustrates a class named ProductService that uses a factory to create DbContext instances for each scope of work.

public class ProductService
{
    private readonly IDbContextFactory _factory;
    public ProductService(IDbContextFactory factory)
        => _factory = factory;
    public async Task GetByIdAsync(int id)
    {
        await using var context = await _factory.CreateDbContextAsync();
        return await context.Products.FindAsync(id);
    }
    public async Task UpdateStockQuantityAsync(int id, int updateQuantity)
    {
        await using var context = await _factory.CreateDbContextAsync();
        var product = await context.Products.FindAsync(id);
        if (product is null) return;
        product.Quantity += updateQuantity;
        await context.SaveChangesAsync();
    }
}

Note that ProductService has two methods, GetByIdAsync and UpdateStockQuantityAsync. An instance of the DbContext class is created locally in each of these methods. Now, suppose you have two threads, T1 and T2, that execute these methods concurrently. That is, thread T1 executes the GetByIdAsync method while thread T2 executes the UpdateStockQuantityAsync method. Because each of these methods is executed in isolation, they will have their own context, connection, and change-tracking information, and there will be no mutable state, so you don’t need to implement thread synchronization in either of these methods.

Consider the following code that executes a read operation and an update operation in two separate tasks.

public static async Task RunMethodsInParallelAsync(ProductService productService)
{
      Task readTask = productService.GetByIdAsync(1);
      Task updateTask = productService.UpdateStockQuantityAsync(3, 5);
      await Task.WhenAll(readTask, updateTask);
      Product? product = await readTask;
 }

The Task.WhenAll method runs the two tasks in parallel and waits until both have finished. The reason this approach is thread-safe, and will not create concurrency errors, is that each of these two methods creates its own DbContext instance internally. Therefore the read operation and the update operation use independent DbContext instances.

Use DbContext pooling to reduce allocation cost

Although creating DbContext instances is not that costly, you should consider using pooled contexts in applications that require high scalability and high performance. The following code snippet shows how you can register a pooled context.

builder.Services.AddPooledDbContextFactory(options =>
    options.UseSqlServer(connectionString));

A call to AddDbContext() will register a DbContext instance as scoped per HTTP request. Each request will run on a different thread and each will have its own context. However, keep in mind that the default scoped registration of the DbContext will not always suffice.

You will need a factory to create instances of DbContext when you’re using a background service, or performing some work inside a particular request, or running some business logic operation over multiple contexts.

Key takeaways

  • If you use EF Core in the data access layer of your application, you must implement thread safety measures whenever you run your queries in parallel.
  • You cannot execute multiple queries in parallel in EF Core using the same DbContext instance.
  • The IDbContextFactory enables you to create a DbContext instance for each thread, thereby enabling you to work with these instances in isolation.
  • Although using a DbContext pool involves a small allocation overhead, it becomes a non-issue if you need high throughput.

  •  

New pip flag fixes longstanding Python frustration

A new version of Python’s native package management tool, pip, will remove a limitation that has frustrated Python developers for years. If you wanted to install the dependencies for a given package, but not install the package itself, you were stuck. Either you had to extract the dependency list from the package and install it by hand, or you had to build the whole package anyway.

Why was this a problem? Sometimes, you want only the dependencies for a package—for instance, as a way to create a separate environment for testing or another project. If you’re making source distributions via CI, some requirements might be needed to make the source distribution but aren’t actually included in it (e.g., Cython). You would need to install these requirements somewhere—apart from the project itself—to perform the build step.

A dependencies-only install mode for packages is a long-requested feature. Developer James O’Claire found many examples of such requests, along with various workarounds. Most of those involved third-party solutions of some sort.

Now, a new feature set to land in pip version 26.2 will fix this problem from the inside. The command pip install --only-deps will install only the dependencies for a given package. Note that it will not install build dependencies for the package, but only runtime dependencies.

Note that you can accomplish this with existing third-party tools. For instance, uv sync --no-install-project has the same behavior. But having this functionality right inside pip means you don’t have to turn to external tooling—or ugly hacks—to solve the problem. This may be an example of how third-party projects like uv (a superfast Python package installer) are inspiring native Python features where they make sense.

pip 26.2 is scheduled to be released by the end of July 2026.

  •  

G# language for .NET borrows from Go, Kotlin, and Swift

G# (GSharp) is moving forward as a programming language for Microsoft’s .NET platform, touted as bringing Go-, Kotlin-, and Swift-style ergonomics to the CLR (Common Language Runtime). The language is described by its creators as modern, simple, and accessible.

Although pre-1.0 and still growing, G# aims to be for people who want a small, predictable language with direct access to the .NET ecosystem. Developers will see imports, func, structs, slices, maps, channels, go, select, and for in iteration. Also important are nullable flow, direct calls into the CLR (Common Language Runtime), and built-in concurrency.

With G#, copyrighted in 2026, developers get value-oriented structs, reference-oriented classes, data structs, and data classes. For concurrency, G# uses scope for structured concurrency, async func and await for task-based asynchrony, and async sequence[T] for asynchronous streams. G# also makes use of the same Task and Task[T] types familiar from the .NET BCL (Base Class Library).

G# documentation is available on the GitHub site of Microsoft software engineer David Obando. “Every .NET type—your packages, third-party NuGet packages, the BCL—is callable from G# with the syntax you already know. CLR generics use G#’s bracket spelling, and method calls, properties, indexers, and for in over IEnumerable[T] all just work,” according to the website.

A Visual Studio Code extension for G3 can be found at marketplace.visualstudio.com. The extension adds syntax highlighting, language server features, build/run commands, and debugger configuration for .gs and .gsproj files. Developers can install the extension from within VS Code (search for “G#” in the Extensions view) or from the command line.

  •  

‘The Java Story’ recounts the rise, fall, and rise again of Java

The evolution of Java is the subject of a just-released documentary about the programming language and development platform. “The Java Story: The Official Documentary” tells the story of Java through interviews with the engineers who created it and shepherded it through three decades.

Produced by CultRepo and sponsored by Oracle, JetBrains, IBM, and Azul, the documentary follows Java from its set-top box and browser-based origins at Sun Microsystems in the 1990s and through its rise to dominate server-side computing in the 2000s, the “dark ages” and resurgence with Java 8 under Oracle in the 2010s, and its continuing modernization and promising role in AI today. “From its humble beginnings as a project code-named ‘Oak’ at Sun Microsystems to becoming a global standard for enterprise software and billions of devices, Java’s journey is one of radical innovation, strategic pivots, and enduring community strength,” said Cult.Repo.

The documentary also delves into Sun’s bitter Java licensing dispute with Microsoft, Oracle’s suit of Google over its use of Java APIs Android (Google won), the creation of the Java Community Process, Sun’s open-sourcing of Java, and Oracle’s switch to the six-month release cycle. Technical enhancements such as lambda expressions in Java 8, virtual threads in Java 21 (Project Loom), and the ongoing refactor to bring value objects to the Java object model (Project Valhalla) also get attention.

Technical experts and other Java figures interviewed in the documentary include James Gosling, creator of Java; Kim Polese, Java’s first product manager; Carla Schroer, director of Java compatibility at Sun Microsystems; James Duncan Davidson, creator of Apache Tomcat; Mark Reinhold, chief architect of the Java Platform Group at Oracle; Brian Goetz, Java language architect in the Java Platform Group at Oracle; Rod Johnson, creator of Spring; and Gavin King, creator of Hibernate.

  •  

Microsoft releases .NET 11 Preview 6

Preview 6 of Microsoft’s .NET 11 software development platform is now available, with improvements across the runtime, libraries, frameworks, and C# and F# programming languages.

Announced July 14, .NET 11 Preview 6 can be downloaded from dotnet.microsoft.com. The full, general production release is expected in November. The first preview was unveiled February 10.

Runtime-async, the .NET runtime’s built-in implementation of async/await, keeps getting faster in Preview 6, building on the suspension improvements from Preview 5. Async continuations can now opt out of ExecutionContext capture and restore. And the JIT now compiles a dedicated runtime-async version of a synchronous, task-returning method rather than delegating to it through a thunk. The JIT turns the method’s tail calls into runtime-async calls and awaits the task that would otherwise have been returned, so the async path no longer pays for an extra layer of indirection, Microsoft said.

In library improvements, System.Text.Json can now serialize and deserialize the new C# union types. The serializer recognizes a union through the new JsonTypeInfoKind.Union contract type, reads and writes the active case, and supports the reflection-based serializer and the source generator. The new JsonUnionAttribute, JsonUnionCaseInfo, and type-classifier APIs enable customization of how cases are discovered and named.

In C# improvements, extension members now include indexers, so developers can add this[...] access to a type from an extension block. This rounds out the extension members feature introduced in .NET 10, which already supported extension methods and properties. An extension indexer is declared like an instance indexer inside an extension block.

Other highlights in .NET 11 Preview 6:

  • Math.BigMul(long, long, out long) is now significantly faster on x64. The JIT generates a single MUL r/m64 instruction when both operands are 64-bit values and the caller requests the high half of the result. Previously the JIT emitted a helper call. The change eliminates the call overhead and makes code that multiplies large numbers tighter.
  • Minimal API validation in ASP.NET Core now supports asynchronous validators end-to-end.
  • For F#, the compiler emits better sequence points for several common debugging scenarios. Breakpoints and stepping are improved for call arguments that need stack-empty debug points, for expressions and comprehensions, simple literal bindings, and if or match conditions.
  • For .NET MAUI, .NET 11 Preview 6 advances the cross-platform CollectionView and Shell architecture, removes the legacy Xamarin.Forms compatibility package, and brings the Apple workloads onto stable Xcode 26.6.
  • Also for .NET MAUI, HybridWebView JavaScript interop is reworked to use a source generator instead of reflection, so it no longer produces trim or AOT (ahead of time) compiler warnings and works correctly in fully trimmed and Native AOT apps.
  • Projects that use Microsoft.TypeScript.MSBuild in Razor Class Libraries now properly integrate TypeScript compilation outputs with ASP.NET Core Static Web Assets.

  •  

Go-based TypeScript 7.0 arrives

Microsoft has released TypeScript 7.0, a native port of the company’s strongly typed version of the JavaScript language based on the Go programming language.

With this new codebase, TypeScript 7.0 brings native code speed, shared memory multithreading, and a number of new optimizations that typically yield speedups between 8x and 12x on full builds, Microsoft said in a July 8 announcement. This port of TypeScript to native code was done as faithfully as possible, writing new code while maintaining the structure and logic of the original codebase to keep results consistent and compatible between the two compilers, according to Microsoft. 

TypeScript 7.0 is available via npm:

npm install -D typescript

To help with the TypeScript 6.0 to TypeScript 7.0 transition process, Microsoft has published a new compatibility package, @typescript/typescript6. This package provides an executable named tsc6, so that if needed, a developer can install TypeScript 7.0 (which ships its own tsc binary) side-by-side with TypeScript 6.0 without naming conflicts. The new package also re-exports the TypeScript 6.0 API, so that a developer can use tsc for TypeScript 7, while other tooling can continue to rely on TypeScript 6.0.

TypeScript 7.0 is a major milestone in the TypeScript project, Microsoft said. This port has been the primary focus for Microsoft’s team for more than a year. With TypeScript 7.0 now out, the team will return to new feature work, ergonomic improvements, more performance wins, and implementing a new API for the broader ecosystem. Microsoft expects a fairly similar timeline to releases prior to TypeScript 7.0, with new versions published every three to four months.

TypeScript 7.0 was announced as a release candidate on June 18.

  •  

Write cleaner and faster Python code

Meta’s long-awaited Pyrefly linter is out in a 1.0 version, and the forthcoming Python 3.15 has a super-efficient sampling profiler. Plus we have a comprehensive rundown of Python’s indispensable virtual environments — and a warning about a novel breed of malware that exploits Python’s package ecosystem.

Top picks for Python readers on InfoWorld

How to use virtual environments in Python
Isolate and protect your Python projects from each other, and empower them to do more, with virtual environments and their native-to-Python tooling.

Pyrefly 1.0: A fast, forward-looking Python linter
The first full release of Meta’s long-awaited linting and type checking tool for Python delivers speed and offers advanced features for type-checking PyTorch and Django projects.

Hands-on with the new sampling profiler in Python 3.15
Among Python 3.15’s best new features is a sampling profiler, for instrumenting your code and finding its bottlenecks with a minimum of performance impact or fuss. See up-close how it works.

All about Hades, the supply-chain malware that hides in Python packages
It hides in Python packages. It replicates itself across systems. It fools LLM-based code analysis tools into ignoring it. And there may be a lot more like it to come.

More good reads and Python updates elsewhere

Python Steering Council calls for temporary pause on JIT project
The requested pause stays in place until a proper Standards Track PEP lands for the experimental JIT (just-in-time) compiler, the better to describe how the JIT will be a formal and supported part of Python.

Pyodide 314.0: Pyodide packages on PyPI
Thanks to PEP 783, Python packages built with Pyodide (Python ported to WebAssembly) can be installed straight from PyPI instead of through Pyodide — another step closer to Py-on-Wasm becoming an everyday thing.

All about that Python 3.14 garbage collection rigmarole
A new garbage collector introduced in Python 3.14 was yanked at the last minute due to reports of higher memory usage. Here’s a deep dive into what changed for the worse and why.

Are you really expected to run five type checkers now?
No, but you should keep your options open. This blog post from a Pyrefly contributor recommends choosing one of the major offerings (Mypy, Pyrefly, Pyright, ty, Zuban, etc.), but also getting to know the others too.

  •  

Angular Signals in practice: Building a signal-first form in Angular

Understanding a reactivity model in the abstract is useful, but it is ultimately incomplete without seeing how it shapes real application code. Concepts such as state, derivation, and explicit dependencies only become meaningful when they influence how forms are built, validated, and maintained in practice.

In two previous articles, “Angular Signal Forms: From event pipelines to signal-driven state” and “Angular Signals explained: How pull-based reactivity changes how we model state,” we reframed form behavior as a state-driven problem and examined Angular Signals as a pull-based reactivity model well-suited to that kind of work. The natural next step is to apply those ideas to an actual Angular form and observe how the architecture changes when state becomes the primary concern.

This article focuses on a concrete example: a modest but realistic registration form. Rather than introducing new concepts, the goal here is to make earlier ideas tangible. We will see how a signal-backed model reshapes validation, interaction state, and submission logic, and how much coordination logic simply disappears when form behavior is expressed declaratively.

The focus here is not on novelty or completeness, but on making the underlying ideas easier to reason about. By walking through a signal-first form from model definition to submission, we can evaluate whether this approach truly reduces complexity and where it introduces new trade-offs that teams should understand before adopting it more broadly.

Read the series:

Implementing a signal-first registration form

With the conceptual groundwork in place, we can now turn theory into a concrete implementation. In this section, we will build a fully working registration form using Angular’s Signal Forms API. This example is deliberately modest in scope, but it is designed to serve as the foundation for the rest of the series. Each subsequent article will extend this same example rather than introducing a new one.

The form collects an email address, a password, a confirmation password, and explicit acceptance of terms. While simple on the surface, this structure allows us to explore field-level validation, cross-field constraints, interaction state, and submission behavior, all without reverting to event-driven form logic.

Project setup and structure

The example assumes a standard Angular application created with the Angular CLI and configured to use Signals (Angular 17+). The Signal Forms APIs (Angular 21+) live under @angular/forms/signals, which must be explicitly imported.

https://github.com/sonukapoor/angular-signal-forms

The folder structure is intentionally conservative:

src/
  app/
    registration/
      registration.component.ts
      registration.component.html
      registration.model.ts

Separating the model from the component keeps form state independent of presentation. This becomes increasingly valuable as the form grows or is reused across multiple components.

Defining the form model

We begin by defining the shape of the data that the form collects. This is a plain TypeScript interface with no Angular dependencies. Treating the form model as a simple data structure reinforces the idea that the form’s values are just state.

// registration.model.ts
export interface RegistrationData {
  email: string;
  password: string;
  confirmPassword: string;
  acceptedTerms: boolean;
}

This interface mirrors what would typically be sent to a back-end API. There is no duplication of state, no separate “form value” object, and no mapping required at submission time.

Creating the signal-backed form

The form itself is created in the component using a writable signal as the source of truth. The form() function attaches form semantics validation, field state, and submission to that signal.

// registration.component.ts
import { CommonModule } from "@angular/common";
import { Component, signal } from "@angular/core";
import {
  email,
  form,
  FormField,
  required,
  submit,
} from "@angular/forms/signals";
import { RegistrationData } from "./registration.model";

@Component({
  selector: "app-registration",
  imports: [FormField, CommonModule],
  templateUrl: "./registration.html",
  styleUrl: "./registration.css",
})
export class Registration {
  readonly model = signal({
    email: &quot;&quot;,
    password: &quot;&quot;,
    confirmPassword: &quot;&quot;,
    acceptedTerms: false,
  });

  readonly registrationForm = form(this.model, (schema) =&gt; {
    required(schema.email, { message: &quot;Email is required&quot; });
    email(schema.email, { message: &quot;Enter a valid email address&quot; });

    required(schema.password, { message: &quot;Password is required&quot; });
    required(schema.confirmPassword, {
      message: &quot;Please confirm your password&quot;,
    });

    required(schema.acceptedTerms, {
      message: &quot;You must accept the terms to continue&quot;,
    });
  });

  async onSubmit(event?: Event) {
    event?.preventDefault();

    await submit(this.registrationForm, (value) =&gt; {
      console.log(value());
      // Mock Server Call
      return Promise.resolve([
        {
          kind: &quot;EmailAlreadyExists&quot;,
          field: this.registrationForm.email,
          error: { kind: &quot;server&quot;, message: &quot;Email already taken&quot; },
        },
      ]);
    });
  }
}

Several design decisions are worth noting.

First, the model signal is defined as read-only. All mutations to the model occur through form bindings, not ad hoc assignments in the component. This keeps the component declarative and avoids the temptation to manipulate form state imperatively.

Second, validation is declared in one place. The schema function describes constraints on the model without introducing control trees, validator arrays, or observable pipelines. Angular takes responsibility for re-running validation whenever the model changes.

Finally, submission logic is explicit. The submit() helper ensures that the form is valid before invoking the callback, and it passes the current model value directly. There is no need to check flags or manually extract values.

Binding the form to the template

With the form defined, the next step is to bind it to the template. Signal Forms provide the [formField] directive, which connects an input element directly to a field in the form schema.





  
@if (registrationForm().errors().length > 0) {
@for (error of registrationForm().errors(); track error.message) {

{{ error.kind }}

}
}
@if ( registrationForm.acceptedTerms().invalid() && registrationForm.acceptedTerms().touched() ) {

{{ registrationForm.acceptedTerms().errors()[0].message }}

}
@if ( registrationForm.confirmPassword().invalid() && registrationForm.confirmPassword().touched() ) {

{{ registrationForm.confirmPassword().errors()[0].message }}

}
@if ( registrationForm.password().invalid() && registrationForm.password().touched() ) {

{{ registrationForm.password().errors()[0].message }}

}
@if ( registrationForm.email().invalid() && registrationForm.email().touched() ) {

{{ registrationForm.email().errors()[0].message }}

}

What stands out here is the absence of indirection. Each input binds directly to a field. Validation state is accessed through signals such as invalid() and touched(). Error messages are read from a structured error object, not reconstructed manually.

This template contains no subscriptions, no async pipes, and no event handlers for value changes. The UI simply reflects the current form state.

Interaction state and user experience

One of the common criticisms of declarative form models is that they obscure user interaction logic. Signal Forms address this directly by exposing interaction metadata as signals.

The touched() signal determines whether a field has been interacted with. By combining it with invalid(), we control when validation messages appear. This logic remains purely declarative: the template describes when errors should be visible, and Angular ensures the signals stay up-to-date.

The disabled state of the submit button is derived from registrationForm.invalid(). There is no need to manually enable or disable it in response to events. If the form becomes valid, the button is enabled automatically.

Why this scales

Even at this early stage, several advantages of a signal-first form model are apparent. The form’s behavior is expressed in terms of state and derivation, not events. The model, validation rules, and UI bindings are clearly separated. There is no duplication of logic between the component and the template.

As the form grows, this structure holds. Additional fields introduce additional schema entries and template bindings, not new subscription logic. Cross-field validation can be added declaratively. Asynchronous validation and persistence can be layered on without rewriting the core model.

Most importantly, the form remains inspectable. At any point during execution, the model signal reflects the current state of the form. Derived state validity, errors, and UI flags can be understood by reading the code, not by tracing runtime behavior.

What we did not solve yet (and why)

At this stage, it would be easy to walk away with the impression that Signal Forms eliminates most of the hard problems associated with form handling. That impression would be misleading. What we have built so far is intentionally incomplete, not because the approach falls short, but because introducing too much too early obscures the value of the underlying model.

One area we have deliberately postponed is cross-field validation that expresses richer business rules. Many real-world forms depend on relationships between fields rather than isolated constraints. Password confirmation is a familiar example, but more complex scenarios quickly arise in enterprise applications. While Signal Forms support these patterns, introducing them before establishing a clear understanding of derived state risks turns validation back into an imperative exercise rather than a declarative one.

We have also avoided asynchronous validation. Server-backed checks introduce latency, partial failure, cancellation, and race conditions. These are not trivial concerns, and treating them casually often leads to subtle bugs and confusing user experiences. Although Signal Forms provide the necessary hooks to model asynchronous behavior, doing so responsibly requires a careful discussion of pending state, effects, and life-cycle boundaries. That discussion belongs in its own article.

Another omission is persistence and synchronization. Many forms need to autosave drafts, synchronize state with local storage, or react to changes by triggering external side effects. These behaviors are not part of the form state itself; they are consequences of state changes. Treating them as such is essential to keeping the architecture comprehensible. Introducing persistence too early would blur the distinction between state and reaction that this article has worked to establish.

Finally, this article has not addressed migration and interoperability. Few teams are starting from a blank slate. Most will adopt Signal Forms incrementally within applications that already rely on reactive forms or template-driven forms. Hybrid approaches, bridging strategies, and gradual refactors are all critical topics, but they presuppose familiarity with both paradigms. Addressing migration before establishing a solid signal-first mental model would undermine that foundation.

These omissions are intentional. A form architecture that tries to do everything at once often ends up doing nothing clearly. By focusing on the core ideas of state, derivation, and declarative validation, we create a base that can absorb additional complexity without collapsing under it.

Signal Forms in the context of Angular’s evolution

To fully appreciate Signal Forms, it helps to step back and view them not as an isolated feature, but as part of a broader shift in Angular’s design philosophy.

For much of its history, Angular emphasized declarative templates paired with imperative coordination in component classes. RxJS became the backbone of that coordination, providing a powerful abstraction for handling asynchronous workflows, user input, and external events. This model scaled well, but it also encouraged developers to express state indirectly through streams and subscriptions.

Signals represent a deliberate recalibration. They re-center Angular’s reactivity model around state and derivation, rather than events and emissions. This shift is visible across the framework: in component inputs, change detection, and now forms. Signal Forms are not an attempt to replace everything that came before; they are an attempt to make the most common use case, modeling and deriving state, simpler and more explicit.

Framed this way, the design of Signal Forms aligns more closely with state-driven form behavior. The requirement to start with a model signal reflects the idea that the state should have a single, inspectable source of truth. Schema-based validation aligns with the notion that constraints are properties of state, not behaviors triggered by events. Field state exposed as signals reinforces the idea that validity, errors, and interaction metadata are derived values that should be read, not managed.

It is also worth noting that Signal Forms do not attempt to abstract away form behavior. They do not hide form state behind opaque classes or life-cycle hooks. They do not require developers to think in terms of control hierarchies or subscription graphs. Instead, they expose form behavior directly, making it easier to reason about how values, validation, and UI feedback relate to one another.

This approach aligns closely with other recent changes in Angular, including the introduction of modern template control flow and a stronger emphasis on explicit data dependencies. Together, these features point toward a framework that favors clarity over indirection and composition over orchestration.

Importantly, Signal Forms are still evolving. Their APIs may change, and their surface area will almost certainly expand. That is precisely why grounding them in first principles matters. Developers who understand why Signal Forms work the way they do will be far better equipped to adapt as the APIs mature.

This article has intentionally avoided duplicating documentation or enumerating every available feature. Instead, it has focused on establishing a conceptual framework that makes the official APIs feel intuitive rather than surprising. When viewed this way, Signal Forms are not a new way to write forms; they are a clearer expression of what forms have always been.

A new way to think about forms

Building the registration form in this article reveals a quiet but important shift. The reduction in complexity does not come from fewer features or simpler requirements. It comes from expressing form behavior in terms of state and derivation rather than orchestration and reaction.

By treating the data model as the single source of truth, validation rules as declarative constraints, and UI behavior as derived from current conditions, much of the coordination logic that typically surrounds forms becomes unnecessary. There are fewer subscriptions to manage, fewer flags to synchronize, and fewer life-cycle concerns to reason about. Form behavior becomes easier to inspect because it is visible directly in the relationships between values.

This approach does not eliminate the hard problems associated with forms. Asynchronous validation, persistence, and interoperability with existing Angular Forms APIs still require careful design. What changes is where that complexity lives. Instead of being interwoven with state representation, those concerns are layered explicitly on top of a clear foundation.

Signal-first forms are not a universal replacement for existing patterns, nor are they a shortcut to simpler applications. They are, however, a strong example of how aligning APIs with first principles can reduce cognitive overhead and improve maintainability over time. For teams building large, state-heavy forms, this alignment can make the difference between code that merely works and code that continues to evolve without friction.

  •  
❌