Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
156648 stories
·
33 followers

Duende IdentityServer Production Deployment Checklist

1 Share
You've built your instance of Duende IdentityServer, tested it locally, and it works. The login flow is clean, tokens are issued, and your APIs validate them correctly. But production is a different beast. In development, ASP.NET Core quietly generates ephemeral encryption keys, your single process handles every request, and your database is a warm SQLite file on your laptop. Ship that same configuration to production, and you're looking at a CryptographicException on first deploy, broken redirect URIs behind a load balancer, and an operational database that grows out of bounds.
Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

How to Write Your First Quantum Circuit in Python: A Beginner's Step-by-Step Guide

1 Share

Imagine opening your laptop and writing code that follows the laws of Quantum Physics. Sounds like science fiction, right?

That's exactly what I thought the first time I heard about quantum computing. I assumed quantum computers were machines hidden inside secret laboratories. I imagined researchers in white coats working with equipment worth millions of dollars.

Then I discovered something surprising: you can write and run your first quantum program using Python on a regular laptop.

No quantum computer required. No physics degree required. No advanced mathematics required.

Just Python.

In this tutorial, you'll learn how to build your first quantum circuit using Python and Qiskit.

By the end, you'll understand what a quantum circuit is, how qubits work, and how to create one of the most famous experiments in quantum computing called a Bell State.

Let's get started.

Table Of Contents

What Is Quantum Computing?

Most software developers work on regular computers, just like yours - a laptop, smartphone, or gaming console.

Every one of these devices processes information using bits. A bit can only have one value at a time: 0 or 1 Nothing in between.

Quantum computers use something different. They use qubits. A qubit can behave like a 0 and a 1 at the same time until it's measured.

Don't worry if that sounds strange. It sounds strange to everyone the first time.

Think about a coin. When a coin is lying flat on a table, it's either heads or tails. That's exactly how a regular computer bit works. But now, imagine spinning that coin. While it's spinning, it's a blur of both heads and tails at the same time. That's exactly how a quantum bit works

This isn't a perfect explanation. But it's a useful one for beginners.

This ability allows quantum computers to solve certain types of problems differently from regular computers.

Why Should Python Developers Care About Quantum Computing?

You might be thinking: "I'm a Python developer. Why should I learn quantum computing?"

Good question.

The truth is that quantum computing is still in its early stages, but so was artificial intelligence a few years ago. Developers who learn early often gain an advantage.

Python has become one of the most popular languages for quantum programming because it is simple and beginner friendly. Many major quantum platforms provide Python libraries. These include:

Among these options, Qiskit is one of the easiest places to start. That's what you'll use in this tutorial.

If you already know variables, functions, and basic Python syntax, you're ready to begin.

What Is a Quantum Circuit?

If you've built web applications before, you're probably familiar with workflows.

For example:

User clicks button 
        ↓ 
Data is validated 
        ↓ 
Request is sent 
        ↓ 
Response is returned

A quantum circuit works in a similar way. Instead of processing user input, it processes qubits.

A quantum circuit is simply a sequence of instructions applied to qubits.

Here's a simplified view:

Create qubits 
      ↓
Apply quantum gates 
      ↓ 
Measure results 
      ↓ 
Display output

At its core, a quantum circuit simply involves initializing qubits, performing operations, and measuring the results.

Quantum Gates Explained Like a Python Developer

If you've written Python before, you've probably changed values many times.

For example:

light = False

light = not light

print(light)

Output:

True

The not operator changes the value. It takes False and turns it into True.

Quantum computers also need ways to change values. Instead of using operators like not, they use something called quantum gates.

Think of quantum gates as special instructions that tell a qubit what to do.

Just like Python has:

  • not

  • +

  • -

  • *

Quantum computing has:

  • X Gate

  • H Gate

  • CX Gate

Let's understand them one at a time.

X Gate: The Quantum Light Switch

Imagine the light switch in your room.

When the switch is OFF: OFF. Press the switch. Now it becomes: ON. Press it again. It becomes OFF.

The switch keeps flipping between the two states, and that's exactly what the X Gate does.

Classical Example

0 → 1
1 → 0

Quantum Example

qc.x(0)

This means: Apply an X Gate to qubit 0.

If qubit 0 was behaving like a 0, it now behaves like a 1.

If it was behaving like a 1, it becomes a 0.

Think of the X Gate as the quantum version of a light switch or a Python not operator.

H Gate: The Spinning Coin Trick

Now things get interesting. Imagine I place a coin on a table. It can only be Heads or Tails, right?

That's how a normal computer works. A bit is either 0 or 1

Now imagine I spin that coin. While it's spinning, can you confidently say it's heads at any given moment?

No.

Can you confidently say it's tails?

No.

It hasn't landed yet. It's in a special state where both outcomes are possible.

That's the easiest way to think about what the H Gate (or Hadamard Gate) does.

Example

qc.h(0)

This tells Qiskit to put qubit 0 into a superposition.

In beginner language, the qubit is no longer locked to just 0 or just 1. It now has a chance of becoming either when we measure it. Think of it like a spinning coin waiting to land.

Before H Gate:

0

After H Gate:

0 and 1 are both possible

This idea is one of the reasons quantum computers are so powerful.

Instead of exploring only one possibility at a time, they can work with multiple possibilities.

CX Gate: Making Two Qubits Work Together

The CX Gate, also called the CNOT (Controlled NOT Gate), is different from the X and H gates because it works with two qubits instead of one.

To understand how it works, let's use a simple real-life example.

Imagine you and your friend are playing a game. Before the game starts, you both agree on one rule.

If you raise your hand, your friend must immediately switch what they're doing. If they were standing, they should sit. If they were sitting, they should stand.

But if you keep your hand down, your friend does nothing and stays exactly as they are.

Notice something important: your friend's action depends entirely on what you do. They don't decide on their own.

That's very similar to how the CX Gate works.

Here's how we use it in Qiskit:

qc.cx(0, 1)

This line tells Qiskit: "Use qubit 0 to control what happens to qubit 1."

In this case:

Qubit 0 → Control qubit

Qubit 1 → Target qubit

The control qubit makes the decision, and the target qubit responds.

Here's what happens behind the scenes:

If the control qubit is 0, nothing happens. The target qubit stays exactly the same.

If the control qubit is 1, the target qubit flips: 0 becomes 1.

Think of the control qubit as a manager giving instructions to an employee. The employee doesn't act randomly. They only change what they're doing when the manager gives the signal.

By itself, the CX Gate is already useful.

But when we combine it with the Hadamard gate, something amazing happens. The two qubits become connected in a special way called entanglement. You'll learn about that later in this tutorial. Now, it's time to practice what you've learned using Python.

How to Set Up Your Python Environment

Here comes the fun part. Let's prepare your machine. Before you continue, make sure Python is installed on your local computer. For this tutorial, use Python version 3.12.8 or 3.13.8. Those versions work well with all the dependencies you'll be installing.

3.12.8

Step 1: Create a New Project Folder

Create a folder called: quantum-python and then open it in VS Code.

Step 2: Create a Virtual Environment

In your terminal (here I'm using Git Bash), run:

python -m venv .venv

Then activate it. On Windows using Git Bash, run:

source .venv/Scripts/activate

And on MacOS/Linux:

source .venv/bin/activate

Step 3: Install Qiskit

Run:

pip install qiskit qiskit-aer matplotlib

This installs:

  • Qiskit

  • Quantum simulator

  • Chart visualization tools

Step 4: Verify Installation

Create a file called:

test.py

Add:

import qiskit

print(qiskit.__version__)

Run:

python test.py

If you see a version number, you're ready.

Congratulations! You've officially entered the world of quantum programming.

Building Your First Quantum Circuit

Create a new file called bell_state.py. This file will contain your first quantum program.

Now you need to import Qiskit. Add:

from qiskit import QuantumCircuit

qc = QuantumCircuit(2, 2)

This imports the QuantumCircuit class.

What does this mean? QuantumCircuit(2, 2) creates 2 qubits and 2 classical bits.

The classical bits will store the final results after measurement.

Let's print the circuit.

print(qc)

Output:

q_0:
q_1:
c:

Right now, nothing is happening. The circuit is empty. You're about to change that.

Creating Superposition

Let's add our first quantum gate: qc.h(0).

This applies a Hadamard Gate to qubit 0.

Your code becomes:

from qiskit import QuantumCircuit

qc = QuantumCircuit(2, 2)

qc.h(0)

print(qc)

Output:


      ───
q_0: ┤ H  ├
      ───
q_1: ─────
          
c: 2/═════

The H gate places qubit 0 into superposition. This is where quantum behavior begins.

You have officially created your first quantum state.

Creating Entanglement With the CNOT Gate

So far, we've only worked with a single qubit. Let's do something much more interesting.

You can make two qubits work together. This phenomenon is called entanglement.

If you've spent time on tech Twitter or watched science videos on YouTube, you've probably heard people call entanglement "spooky action at a distance."

Don't worry about the fancy name, just focus on the code.

Add this line beneath your Hadamard gate: qc.cx(0, 1).

Your program should now look like this:

from qiskit import QuantumCircuit

qc = QuantumCircuit(2, 2)

qc.h(0)

qc.cx(0, 1)

print(qc)

Output:

      ───     
q_0: ┤  H ├─■──
      ───  ─┴─
q_1: ────┤ X  ├
            ───
c: 2/══════════

But what exactly happened?

The first qubit entered superposition when we applied the H gate. The CNOT gate then linked the second qubit to the first. Now the two qubits behave as a connected system, not two separate pieces of information. Just one shared quantum state.

Think about two perfectly synchronized dice. Every time you roll them, they somehow always show the same number.

Sounds impossible, right? That's because it is impossible in normal classical computing.

But quantum mechanics plays by different rules.

Measuring the Qubits

Right now our qubits exist in a quantum state, but computers can't display quantum states directly.

We need to measure them. Measurement converts quantum information into classical information.

Add the following line: qc.measure([0, 1], [0, 1]).

Your code now becomes:

from qiskit import QuantumCircuit

qc = QuantumCircuit(2, 2)

qc.h(0)

qc.cx(0, 1)

qc.measure([0, 1], [0, 1])

print(qc)

What does this line do?

It means:

  • Measure qubit 0

  • Store result in classical bit 0

and

  • Measure qubit 1

  • Store result in classical bit 1

At this point our circuit is complete. Now we need to execute it.

Running the Circuit on a Quantum Simulator

Here's the cool part. You don't need a quantum computer. Your laptop can simulate one.

Create a new section beneath your circuit.

from qiskit_aer import AerSimulator

simulator = AerSimulator()

result = simulator.run(
    qc,
    shots=1024
).result()

counts = result.get_counts()

print(counts)

Let's break it down.

What Is AerSimulator That You Installed?

AerSimulator is Qiskit's local quantum simulator.

Instead of sending your program to a real quantum machine, it runs everything on your computer.

This is perfect for learning and experimentation, and it's completely free.

What Are Shots?

Notice this line: shots=1024.

A shot is a single execution of the quantum circuit. Quantum outcomes are probabilistic, which means that one execution isn't enough.

Running 1,024 shots lets us see the overall pattern.

Think of it like flipping a coin. One flip tells you nothing but a thousand flips reveal the probabilities.

Your Complete Bell State Program

At this point your file should look like this:

from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator

qc = QuantumCircuit(2, 2)

qc.h(0)

qc.cx(0, 1)

qc.measure([0, 1], [0, 1])

simulator = AerSimulator()

result = simulator.run(
    qc,
    shots=1024
).result()

counts = result.get_counts()

print(counts)

Save the file.

Run: python bell_state.py.

You should see something similar to:

{
    '00': 504,
    '11': 520
}

Your numbers will be slightly different, which is normal. The important thing is that you see: 00and 11.

You should never see: 01 or 10

And that's the clue that tells us entanglement is working.

For Windows Users: A Common Qiskit Aer Error

If you're using Windows, you might run into this error when importing AerSimulator:

ImportError: DLL load failed while importing controller_wrappers:
The specified module could not be found.

This usually isn't a problem with your code. It happens because Microsoft Visual C++ Redistributable 2015–2022 (x64) isn't installed on your system.

To fix it:

  1. Download and install the Microsoft Visual C++ Redistributable 2015–2022 (x64) from the official Microsoft website.

  2. Restart your computer.

  3. Reopen your terminal and run your program again.

Once the runtime is installed, AerSimulator should import successfully, and you can continue with the rest of the tutorial.

Other Common Mistakes Beginners Make

If your code doesn't work immediately, don't panic. Everyone hits errors.

Common issues include:

Module Not Found

ModuleNotFoundError

Solution: pip install qiskit.

Wrong Virtual Environment

Make sure your virtual environment is activated before running the script.

Missing Simulator

Install: pip install qiskit-aer.

Indentation Errors

Remember that Python cares about spacing. Check your indentation carefully.

Visualizing Results With a Histogram

Developers love visual feedback. A chart makes quantum behavior easier to understand. You can create one.

Add:

from qiskit.visualization import plot_histogram
import matplotlib.pyplot as plt

plot_histogram(counts)

plt.show()

Your bell_state.py file will now look like this:

# IMPORT DEPENDENCIES
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram 
import matplotlib.pyplot as plt

# Create a Quantum Circuit with 2 qubits and 2 classical bits
qc = QuantumCircuit(2, 2)

# Create a Bell state (entanglement) using a Hadamard and a CNOT gate
qc.h(0)
qc.cx(0, 1)

# Measure all qubits into their corresponding classical bits
qc.measure([0, 1], [0, 1])


# Initialize the Aer simulator and execute the circuit for 1024 shots
simulator = AerSimulator()
result = simulator.run(
    qc,
    shots=1024
).result()

# Gather the resulting measurement counts
counts = result.get_counts()

# Print raw text counts and plot the histogram data
print(counts)
plot_histogram(counts) 
plt.show()

Run your program again, a histogram should appear.

It will look something like this:

Bell State quantum circuit histogram generated with Python and Qiskit

For the complete project folder, you can get it from Github.

Understanding What Just Happened

Let's pause for a second because something incredible just happened.

  • You created entanglement using Python on your laptop without owning a quantum computer.

  • The first qubit entered superposition.

  • The second qubit became linked to it.

When measurement happened, both became 0 or both become 1. The outcome was random, but they always agreed. That's the key observation.

What Is a Bell State?

The Bell State is one of the most famous examples in quantum computing. It's often the first experiment beginners learn.

Why? Because it demonstrates two important quantum ideas:

  • Superposition

  • Entanglement

Without Bell States, many quantum algorithms wouldn't exist because:

  1. Quantum communication systems depend on them.

  2. Quantum cryptography depends on them.

  3. Future quantum networks depend on them.

The Bell State is basically the "Hello World" of quantum computing. Every quantum developer encounters it sooner or later.

Why Bell States Matter

At first glance, this experiment seems small: Two qubits, Two gates, and a few lines of Python. Yet, the idea behind it is huge.

Bell States do much more than demonstrate entanglement. Researchers use them as benchmark experiments to verify that quantum hardware can reliably create and measure entangled qubits.

For example, Bell State circuits are commonly executed on superconducting quantum processors to evaluate how accurately the hardware prepares entangled states before running more complex quantum algorithms.

Bell States also play an important role in quantum communication and serve as building blocks for larger quantum algorithms.

Think of them like functions in programming. A single function may seem small but complex applications are built from thousands of them.

The same idea applies here. Large quantum systems are built from smaller quantum operations.

Real World Applications of Quantum Entanglement

A common question beginners ask is: "When will I actually use this?"

Fair question.

Here are some real examples.

Quantum Cryptography

While traditional encryption relies on mathematical difficulty, quantum cryptography relies on the laws of physics, ensuring that any attempt to intercept data changes the quantum state and makes eavesdropping immediately detectable.

Quantum Networking

Researchers developing quantum internet technologies are heavily leveraging quantum entanglement to connect quantum devices across large distances.

Drug Discovery

Quantum computers may eventually simulate molecules more accurately than classical computers. This could help researchers discover new medicines, improve materials, and understand chemical reactions.

Financial Modeling

Large financial institutions are exploring quantum algorithms for:

  • Portfolio optimization

  • Risk analysis

  • Market simulation

The field is still developing, but the potential is enormous.

Beginner Experiments to Try

The best way you can learn quantum computing is exactly how developers learn programming:

  • Break things.

  • Experiment.

  • Change the code.

  • Observe the results.

Let's try a few simple experiments.

Experiment 1: Remove the Hadamard Gate

Delete: qc.h(0) and run the circuit again. What changes?

Observe the output. Why do you think that happened?

Experiment 2: Increase the Number of Shots

Change: shots=1024 to shots=100000.

Run the simulation again. Notice how the results become more balanced. This is probability in action.

Experiment 3: Add an X Gate

Insert: qc.x(1) before the CNOT gate.

Run the circuit.

While studying the new output distribution, try predicting the results before running the code.

Experiment 4: Create Three Qubits

Change: QuantumCircuit(2, 2) to QuantumCircuit(3, 3).

Can you create a larger entangled system? Experiment and see.

What Should You Learn Next?

You've now built your first quantum circuit. That's a big milestone.

Here are some great next steps you can explore through IBM Quantum Platform:

  • X Gate

  • Y Gate

  • Z Gate

  • S Gate

  • T Gate

Build Larger Circuits

Try:

  • GHZ States

  • Quantum Teleportation

  • Deutsch Algorithm

Explore Real Quantum Hardware

IBM allows developers to run circuits on actual quantum computers. This is one of the coolest experiences in modern programming.

Learn Quantum Algorithms

Once you're comfortable with circuits, explore:

  • Grover's Algorithm

  • Shor's Algorithm

  • Quantum Fourier Transform

Final Thoughts

A few years ago, quantum computing felt impossible to approach. It seemed reserved for physicists and researchers.

Today, that's no longer true. If you know Python, you already have a pathway into quantum development.

In this tutorial, you learned:

  • What quantum computing is

  • How qubits differ from bits

  • What quantum gates do

  • How to install Qiskit

  • How to create a Bell State

  • How to simulate a quantum circuit

  • How to visualize results

  • Why entanglement matters

Most importantly, you wrote your first quantum program. That's how every quantum developer starts.

One circuit. One experiment. One curiosity-driven question at a time.

Now open your editor and modify the code. Break things. Try new gates. And start exploring the quantum world for yourself.



Read the whole story
alvinashcraft
20 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

.NET 5 to 10: Key Features Introduced in Every Release

1 Share

Every November, .NET Conf brings a new major release of .NET. If you have been building on the platform since the early 2020s, you have watched the ecosystem move from “.NET Core” to a single, unified .NET — and pick up minimal APIs, Native AOT, Blazor full stack, Aspire, and a steady stream of C# language improvements along the way.

This post is a guided tour of what landed in each major version from .NET 5 through .NET 10. It is not exhaustive — Microsoft ships hundreds of changes per release — but it should help you remember which version introduced what, and where to dig deeper.

Version Released Support type C# version Status (June 2026)
.NET 5 Nov 2020 STS (18 months) C# 9 End of support
.NET 6 Nov 2021 LTS (3 years) C# 10 End of support
.NET 7 Nov 2022 STS (18 months) C# 11 End of support
.NET 8 Nov 2023 LTS (3 years) C# 12 Supported
.NET 9 Nov 2024 STS (2 years) C# 13 Supported
.NET 10 Nov 2025 LTS (3 years) C# 14 Current LTS
.NET 11 Nov 2026 Preview C# 15 Preview Until Nov 2026 then STS

For the full release schedule and patch versions, see the official .NET releases documentation.

.NET 5 — November 2020

.NET 5 was the first release after the “Core” branding was dropped. Version 4.x was skipped deliberately so nobody confused it with .NET Framework 4.x. The message was clear: this is the main implementation of .NET going forward, not a side project.

Platform and runtime

  • Unified vision — one BCL and runtime targeting cloud, desktop, web, mobile, and IoT (the full unification completed in .NET 6).
  • Single-file applications and improved app trimming for smaller deployments.
  • ClickOnce publishing for .NET (Windows).
  • ARM64 support on Windows.
  • Performance work across the runtime, GC, and networking stack.

C# 9

C# 9 shipped alongside .NET 5 and brought several features that are now everyday tools:

  • Records — reference types with value-based equality and with expressions for non-destructive mutation.
  • Init-only setters — immutable object initialisation without verbose constructors.
  • Top-level statements — less boilerplate in small console apps.
  • Pattern matching — relational patterns (>, <, and, or, not) and improved switch expressions.
  • Source generators — compile-time code generation (the foundation for later JSON and logging generators).

Libraries and frameworks

  • EF Core 5 — split queries, table-per-entity (TPE) mapping, and Cosmos DB improvements.
  • ASP.NET Core in the 5.x line — gRPC improvements, HTTP/2 and HTTP/3 groundwork, and Blazor WebAssembly performance gains.
  • System.Text.Json — constructor and parameterised constructor support, [JsonIgnore] on properties, and better options for ignoring cycles.

.NET 5 reached end of support in May 2022. If you still have projects on it, plan an upgrade — there are no security patches.

.NET 6 — November 2021 (LTS)

.NET 6 delivered on the unification promise: one SDK, one base class library, and one runtime across mobile, desktop, IoT, and cloud. It was an LTS release and became the baseline many teams standardised on.

Developer experience

  • Hot reload in Visual Studio 2022 and dotnet watch — edit code and see changes without a full restart.
  • Minimal APIs — build HTTP APIs with far less ceremony than traditional controllers.
  • SDK workloads — optional components (MAUI, Blazor WebAssembly AOT, and others) install via dotnet workload instead of bloating the default SDK.
  • Project templates modernised — top-level statements, file-scoped namespaces, nullable reference types, and async Main in new projects.

C# 10

  • global using directives
  • File-scoped namespaces
  • Record structs
  • Improved interpolated strings and constant interpolated strings
  • AsyncMethodBuilder attribute for custom async method builders

Runtime and libraries

  • DateOnly and TimeOnly — separate date and time types without carrying a full DateTime.
  • PriorityQueue<TElement, TPriority> — a built-in min-heap.
  • System.Text.Json source generator, writeable JsonNode DOM, and IAsyncEnumerable serialisation.
  • HTTP/3 preview support via QUIC.
  • OpenTelemetry metrics APIs in System.Diagnostics.Metrics.
  • FileStream rewrite for better async performance on Windows.
  • Arm64 support for macOS (“Apple Silicon”) and Windows, with side-by-side x64 and Arm64 installs.

ASP.NET Core 6

  • Minimal APIs as a first-class style for microservices.
  • Blazor components renderable from JavaScript; AOT compilation for Blazor WebAssembly.
  • Improved gRPC and SignalR performance.

.NET MAUI

.NET Multi-platform App UI arrived in preview with .NET 6 — one codebase for Android, iOS, macOS, and Windows native apps. GA followed in 2022.

.NET 6 LTS ended in November 2024.

.NET 7 — November 2022

.NET 7 was a standard-term support release focused heavily on performance, cloud-native patterns, and polishing the developer experience. I wrote about upgrading to .NET 7 at the time — mostly smooth, with a few surprises around AutoMapper and EF Core triggers.

C# 11

  • Raw string literals — multi-line strings without escape-character pain.
  • required members — enforce initialisation at compile time.
  • Generic math — static abstract interface members for numeric generics.
  • List patternsif (list is [var first, .., var last]).
  • UTF-8 string literalsu8 suffix for byte-oriented APIs.

ASP.NET Core 7

  • Rate limiting middleware — built-in protection against abuse.
  • Output caching — cache entire responses at the framework level.
  • Minimal API improvements — route groups, typed results, and filters.
  • Native AOT for console apps — smaller, faster-starting binaries (ASP.NET Core AOT came in .NET 8).

Performance and tooling

  • Continued JIT and GC improvements; Native AOT publishing matured.
  • Regex source generator for compile-time pattern compilation.
  • Library multi-targeting and package validation tooling for NuGet authors.

.NET 7 reached end of support in May 2024.

.NET 8 — November 2023 (LTS)

.NET 8 is the current-generation LTS baseline for many production workloads (supported until November 2026). It is the release where cloud-native tooling, Native AOT for web apps, and modern C# really clicked together.

C# 12

  • Primary constructors — declare constructor parameters on the type declaration.
  • Collection expressions[1, 2, 3] syntax for arrays, spans, and lists.
  • Alias any typeusing Point = (int X, int Y);
  • Default lambda parameters — optional parameters in lambdas.

ASP.NET Core 8

  • Blazor full stack — server-side rendering, streaming rendering, and enhanced interactivity models in one framework.
  • Native AOT for ASP.NET Core — ahead-of-time compilation for web workloads.
  • Keyed dependency injection — register and resolve multiple implementations of the same interface by key.
  • Frozen collections — optimised read-only collections for hot paths.
  • TimeProvider — abstract time for testable code.

.NET Aspire (now just called Aspire)

.NET Aspire launched in preview with .NET 8 — an opinionated stack for observable, distributed applications. AppHost orchestrates dependencies; ServiceDefaults wire up OpenTelemetry, health checks, and resilience. I have been using it for local microservice development and it removes a lot of port-and-connection-string drudgery. In 2025 .Net Aspire was rebranded to Aspire and version 13 was released (don’t ask why 13, but I believe it was so it was no longer tied to .NET releases)

Other highlights

  • EF Core 8 — complex types, primitive collections, JSON column mapping, HierarchyId.
  • NuGet Audit — warnings when packages have known vulnerabilities.
  • Windows Forms and WPF improvements — DPI, data binding, and hardware acceleration.
  • Source generators for COM interop and configuration binding.

.NET 9 — November 2024

.NET 9 continued the annual cadence with performance, AI integration, and polish across the stack. I covered several highlights in my .NET 9 post when it shipped.

C# 13

  • params collectionsparams works with Span<T>, ReadOnlySpan<T>, and collection types beyond arrays.
  • lock on System.Object — the lock statement can use more types safely.
  • Partial properties and indexers — split declarations across files.
  • Escape sequence \e for the ESC character.

Platform and libraries

  • Performance — runtime, GC, and JIT improvements across the board; the team publishes detailed benchmark posts each release.
  • AI abstractionsMicrosoft.Extensions.AI packages unify access to language models and embeddings from different providers.
  • NuGet Audit defaults to reporting transitive dependency vulnerabilities (opt back to direct-only with <NuGetAuditMode>direct</NuGetAuditMode>).
  • OpenTelemetry enhancements and better observability defaults.

ASP.NET Core 9 and Blazor

  • MapStaticAssets() — fingerprinted, cache-friendly static files replace UseStaticFiles() for Blazor and modern web apps.
  • Blazor component state persistence, improved form handling, and better WebAssembly performance.
  • .NET Aspire 9 — dashboard improvements, deployment tooling, and more community integrations.

.NET 9 is standard-term support until November 2026.

.NET 10 — November 2025 (LTS)

.NET 10 is the current LTS release (supported until November 2028). If you are choosing a version for a new project in 2026, this is the long-term bet.

C# 14

  • Field-backed properties — use the field keyword when you outgrow auto-properties.
  • nameof for unbound generics — e.g. nameof(List<>).
  • Implicit Span<T> conversions — less ceremony at API boundaries.
  • ref/out/in in lambdas without explicit parameter types.
  • Extension blocks — static extension methods and properties in a dedicated syntax.
  • Null-conditional assignment (?.=) and user-defined compound assignment operators.

Runtime and libraries

  • JIT improvements — better inlining, devirtualisation, and loop inversion.
  • AVX10.2 support for SIMD workloads.
  • Native AOT enhancements and improved struct argument code generation.
  • JSON — stricter serialisation options, PipeReader support, duplicate property handling.
  • Post-quantum cryptography — expanded ML-DSA and related APIs.
  • WebSocketStream — simpler WebSocket usage; TLS 1.3 on macOS clients.

SDK and tooling

  • dotnet test integrates Microsoft.Testing.Platform.
  • Container images from console apps without a separate Dockerfile in simple scenarios.
  • dotnet tool exec for one-shot tool runs.
  • Native shell tab-completion scripts for the CLI.

ASP.NET Core 10 and Blazor

  • Blazor WebAssembly preloading and automatic memory pool eviction.
  • Passkey support for ASP.NET Core Identity.
  • OpenAPI and minimal API improvements.

One breaking change caught me when upgrading a Blazor WebAssembly app: HttpClient response streaming is enabled by default, which broke synchronous Stream.Read calls in generated API clients. I wrote about the fix in Blazor and .NET 10 — either opt out with <WasmEnableStreamingResponse>false</WasmEnableStreamingResponse> or move to async reads.

EF Core 10, MAUI, and desktop

  • EF Core 10 — named query filters, Cosmos DB improvements, and LINQ performance work.
  • .NET MAUI 10 — multi-file MediaPicker, WebView interception, Android API 35/36 support.
  • Windows Forms and WPF — continued quality and Fluent-style updates.

How to choose a version in 2026

Scenario Recommendation
New production app, minimise upgrade churn .NET 10 LTS
Existing app on .NET 8 LTS with no pressing need Stay on 8 until you are ready; both 8 and 10 are supported
Experimenting with the latest features .NET 9 or .NET 10 — check STS/LTS dates
Legacy maintenance Upgrade anything still on 5, 6, or 7 — they are all end-of-life

Microsoft’s guidance has shifted over the years: LTS releases (6, 8, 10) alternate with STS releases (5, 7, 9) on an annual November cadence. STS now lasts two years; LTS lasts three. Remember there is no difference in quality between a LTS and STS release, it is only the support window that is different.

Further reading

If you have been on the platform since .NET 5, you have lived through the best decade of .NET since the original framework shipped.



Read the whole story
alvinashcraft
40 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

How a Bloom Filter Works: Build One From Scratch in Python

1 Share

A Bloom filter gives you something that feels like magic: it can tell you whether an item is in a set of billions, using only a few kilobytes of memory. And it answers in the same tiny amount of time no matter how much you have stored.

That sounds impossible. A normal set has to remember every item, so its memory grows with the data. But a Bloom filter remembers almost nothing about the items themselves, yet it still answers membership questions. The catch is that it's allowed to be wrong in one specific, controllable direction.

It's not magic, and the moment you build one yourself, the trick becomes clear and you should understand exactly what it can and can't promise.

In this tutorial, we'll build a working Bloom filter from scratch in Python, using nothing but a list of bits and a couple of hash functions. By the end, you'll understand bit arrays, why we use several hashes, what a false positive is, the one guarantee a Bloom filter never breaks, and how to size one for a target error rate.

Table of Contents

What a Bloom Filter Actually Is

A Bloom filter is a probabilistic data structure. Its whole job is to answer one question, "is this item in the set?", and it gives one of only two answers:

  • Definitely not in the set. This answer is always correct.

  • Possibly in the set. This answer is usually correct, but it's occasionally wrong.

The surprising part is that it answers without storing the items at all. A normal set, like Python's set or a hash table, keeps every item it has seen, so its memory grows with both the number of items and the size of each one.

A Bloom filter keeps only a fixed row of bits. Its size is decided up front and never changes, whether you store short words or long URLs or whole files.

So a Bloom filter isn't really a container. It's closer to a fingerprint of a set. You can't ask it to list what's inside, or to hand an item back. You can only ask "have you probably seen this?", and you can trust its "no" completely.

A quick way to picture it: instead of keeping a guest list of names, you keep a wall of light switches. When a guest arrives, you flip a few switches chosen from their name. To check whether someone came, you look at their switches. If any one of them is off, they definitely never arrived. If all of them are on, they probably did, though someone else's name might have flipped those same switches.

That picture also explains why you would reach for one instead of a plain set. For a million URLs averaging fifty bytes each, a real set costs tens of megabytes and grows with the length of the URLs. A Bloom filter for the same million items at a one percent error rate costs about 1.2 megabytes, fixed, no matter how long the URLs are.

When the set is huge, has to live in memory on every machine, or holds large items, that saving is the difference between practical and impossible. The price is the rare false positive, and the usual pattern makes that cheap: a "no" skips an expensive lookup, and a "yes" just triggers the slower exact check you would have run anyway.

The rule of thumb: if you need exact answers, deletion, or the ability to list what is stored, use a real set. If you need a tiny, fast gate that sits in front of an expensive operation and reliably tells you when you can skip it, use a Bloom filter.

A Short History

The structure is named after Burton Howard Bloom, who described it in a 1970 paper, "Space/Time Trade-offs in Hash Coding with Allowable Errors", in Communications of the ACM.

His motivating example was wonderfully ordinary. A program that hyphenated and spell-checked text needed to look words up in a dictionary, and storing the whole dictionary in the tiny memories of 1970 was too expensive. Bloom's idea was to accept a small, controlled rate of mistakes in exchange for a large saving in space. That single trade, allow a little error and save a lot of memory, is why the structure still turns up in so many large systems more than fifty years later.

Where Bloom Filters Are Used

You've very likely used software backed by a Bloom filter today. They're important in:

  • Databases and storage engines: Cassandra, HBase, Bigtable, and many log-structured (LSM-tree) stores keep a Bloom filter for each on-disk file. Before a slow disk read, the engine asks the filter "could this key be in this file?" A "no" lets it skip the file entirely, which avoids a huge number of reads.

  • Safe browsing: Early versions of Google Chrome checked each URL against a local Bloom filter of known-dangerous sites. A "no" meant safe, with no network call. A "yes" was rare and triggered a real check against the full list.

  • Caches and CDNs: A common trick is to cache an item only after it has been requested at least twice. A Bloom filter cheaply remembers "have I seen this once before?", which filters out the flood of one-time requests.

  • Recommendations: Medium has described using a Bloom filter to avoid recommending articles you've already read.

  • Networking and crypto: Routers use them to spot duplicate packets, and early Bitcoin light clients used them to request relevant transactions without revealing exactly which addresses they cared about.

The shape is always the same. A Bloom filter stands in front of something expensive (a disk read, a network request, a database query) and turns most of those expensive checks into a couple of fast array reads. Now let's build one and see exactly how.

The Core Idea: a Bit Array and a Few Hashes

A Bloom filter is built on two pieces:

  1. A bit array: a long row of bits, all starting at 0.

  2. A handful of hash functions that each turn an item into a position in that array.

To add an item, you run it through each hash function, get several positions, and set the bit at each of those positions to 1.

To check an item, you run it through the same hash functions and look at those same positions. If every one of them is 1, the item is "probably present". If even one is 0, the item is "definitely absent".

That second answer is the important one. If a bit is still 0, you know for certain you never added anything that would have set it. The filter never misses something it has actually seen.

Here's the whole structure in Python:

import hashlib

class BloomFilter:
    def __init__(self, size, num_hashes):
        self.size = size              # number of bits in the array (m)
        self.num_hashes = num_hashes  # number of hash functions (k)
        self.bits = [0] * size        # every bit starts at 0

Turning an Item into Positions

We need num_hashes different positions for each item, and they need to be spread out. A common, clean trick is double hashing: compute two independent hashes once, then combine them to produce as many positions as you need.

def _positions(self, item):
    data = item.encode("utf-8")
    h1 = int.from_bytes(hashlib.sha256(data).digest()[:8], "big")
    h2 = int.from_bytes(hashlib.md5(data).digest()[:8], "big")
    for i in range(self.num_hashes):
        yield (h1 + i * h2) % self.size

Three things are happening:

  • sha256 and h2 from md5 give us two big numbers that are stable for the same string and look random across different strings.

  • h1 + i * h2 mixes them into a different value for each i, so the positions scatter instead of clumping together.

  • % self.size folds each value into a valid index, from 0 to size - 1.

Run this for one item and you get num_hashes positions. Those positions are the item's fingerprint inside the filter.

Adding and Checking

Adding sets the bit at every position. Checking asks whether they're all set.

def add(self, item):
    for idx in self._positions(item):
        self.bits[idx] = 1

def __contains__(self, item):
    return all(self.bits[idx] for idx in self._positions(item))

Defining __contains__ lets us use Python's natural in syntax. Let's try it:

bf = BloomFilter(size=1000, num_hashes=4)
bf.add("alice")
bf.add("bob")

print("alice" in bf)   # True
print("bob" in bf)     # True
print("carol" in bf)   # almost always False

"carol" was never added, so at least one of its four bits is almost certainly still 0, and the filter reports absence. That's the common case. But notice the words "almost certainly". That hedge is the whole story of the next section.

False Positives Are Normal

Bits are shared. With enough items added, the four bits that happen to encode "carol" might all have been set to 1 by other items, even though "carol" itself was never added. When that happens, the filter says "probably present" for something that's absent. That's a false positive.

People new to Bloom filters sometimes think this is a bug. It's not. It's the price you pay for using so little memory, and it's tunable. You can watch it happen by cramming many items into a small filter:

bf = BloomFilter(size=200, num_hashes=4)
for i in range(100):
    bf.add(f"user-{i}")

# None of these were added, but some will sneak through as "present":
false_hits = sum(f"ghost-{i}" in bf for i in range(1000))
print(false_hits)  # a non-zero number: the false positive rate in action

The filter is never wrong in the other direction, though. Every user-i you added still returns True, because adding an item sets all of its bits, and those bits never get cleared. This is the one promise a Bloom filter always keeps:

  • A "no" is always correct. No false negatives, ever.

  • A "yes" might be wrong. False positives are possible.

That asymmetry is exactly what makes Bloom filters useful. A web browser can keep a Bloom filter of known-malicious URLs and check every link instantly. A "no" means the link is safe and needs no further work. A "yes" is rare and just triggers a slower, exact check against the real list. The filter turns most lookups into a couple of array reads.

Sizing it for a Target Error Rate

The false positive rate depends on three numbers: the bit array size m, the number of items you expect to add n, and the number of hash functions k. The approximate false positive rate is:

p = (1 - e^(-k*n/m)) ** k

You don't have to guess these. Given the number of items n and a target false positive rate p you can pick the best m and k directly:

import math

def optimal_params(n, p):
    m = math.ceil(-n * math.log(p) / (math.log(2) ** 2))  # bits needed
    k = max(1, round((m / n) * math.log(2)))               # hashes to use
    return m, k

print(optimal_params(1_000_000, 0.01))  # about (9_585_059, 7)

Read that result carefully. To track one million items with a one percent error rate, you need roughly 9.6 million bits, which is about 1.2 megabytes, and 7 hash functions.

A real set of one million strings would cost far more, and most of that cost grows with the length of the strings. The Bloom filter doesn't care how long the items are, only how many there are.

What it Cannot Do: Delete

There's one more honest limitation. You can't remove an item by clearing its bits, because those bits are shared. Clearing the bits for "alice" might also clear a bit that "bob" depends on, and now "bob" would wrongly report as absent, breaking the no-false-negatives promise.

If you need deletion, the standard fix is a counting Bloom filter, where each slot is a small counter instead of a single bit. Add increments the counters, remove decrements them, and a slot counts as "set" while its counter is above zero. It costs more memory, which is the usual trade.

Putting it Together

Here's what we built and what it costs:

Operation Cost
add O(k)
in (check) O(k)
space about m bits for n items, independent of item size

The takeaways:

  • A Bloom filter is a bit array plus a few hash functions. Adding sets k bits, checking asks whether those k bits are all set.

  • A "no" is always correct. A "yes" can be a false positive, and the rate is something you tune with m and k.

  • It's tiny and fast because it stores fingerprints, not the items, so it forgets what the items actually were.

  • It can't delete without a counting variant, because bits are shared.

The next time a system tells you "this is definitely not in the cache, skip the lookup" or "this might be a known item, let me double-check", you'll know exactly what's underneath: a row of bits, a few hashes, and one carefully chosen direction in which it's allowed to be wrong.

If you enjoy learning data structures by building them rather than memorizing them, that's the idea behind a learn-by-doing platform I built called IWTLP, where this Bloom filter is one of the build-it-yourself exercises in the data engineering track.



Read the whole story
alvinashcraft
52 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

How to Use Claude Code to Build Flutter Apps Faster — Best Practices for 2026

1 Share

In early 2023, I was interning at a US-based company, long before agentic AI became part of everyday development.

We had tools like ChatGPT, Gemini, and Copilot, but they were mostly chat interfaces: you pasted code, got a response, and moved on.

During that time, my manager, who worked in AI/ML, told me that a day would come when developers would collaborate with AI agents and that learning how to write effective prompts would become a valuable skill.

I took that advice seriously. I spent countless nights experimenting with prompts, refining instructions, and learning how to communicate with AI systems effectively.

Today, while I still write code by hand and believe strongly in fundamentals, those early lessons have paid off. In an era where AI is embedded into the development workflow, I've been able to leverage it to significantly amplify my productivity as a software engineer.

You've probably seen all the excitement around AI coding assistants. But if you've tried using one on a real Flutter project, whether it's a fintech app, an e-commerce platform, or any application with a well-structured architecture, you've likely experienced the frustration, too.

The assistant generates a widget. You paste it in. It doesn't fit your architecture. It ignores your naming conventions. It recreates functionality that already exists somewhere else in your codebase. Before long, you've spent twenty minutes fixing code that was supposed to save you time.

The problem isn't the AI. The problem is that most developers still use AI as an advanced autocomplete tool when it can function as something much more powerful: a second engineer that understands your codebase, follows your conventions, and tackles parallel tasks while you focus on solving the hard problems.

In this article, I'll show you what has actually worked for me. We'll cover how to structure your Flutter projects so Claude Code can navigate them effectively and how to use skills, loops, and subagents to automate repetitive development tasks and dramatically increase your productivity.

Prerequisites

Before following along, you should be comfortable with the basics of Flutter development; building widgets, managing state, and running the app from the terminal. You don't need to be an expert.

On the tooling side, you'll need:

  • Flutter SDK (3.x or later): the framework we're building with. Install it from flutter.dev.

  • Claude Code: Anthropic's agentic coding tool that runs in your terminal alongside your editor. Install it with npm install -g @anthropic-ai/claude-code, then run claude in your project directory to start a session. You'll need an Anthropic account and API key.

  • A code editor: VS Code or Android Studio both work well. Claude Code operates in the terminal and reads/writes files directly, so it works alongside whatever editor you use.

  • Git: version control is assumed throughout. Claude Code integrates with Git for commits, diffs, and branch awareness.

Here's a quick overview of the Claude Code concepts we'll use throughout the article:

  • CLAUDE.md: a markdown file at your project root that Claude reads at the start of every session. Think of it as a briefing document: your architecture, your conventions, your commands.

  • Skills: reusable instruction packs stored in .claude/skills/. You define them once, and Claude invokes them automatically when the task matches, or you call them manually with /skillname.

  • Subagents: isolated Claude instances that handle a focused task in their own context window, then return only a summary. Great for parallel work without polluting your main session.

  • Hooks: shell commands or scripts that fire on lifecycle events (before a tool runs, after a turn completes, and so on). They bypass Claude's judgment entirely — useful for enforcing rules deterministically.

  • /loop: a built-in skill that reruns a task repeatedly until a condition you define is met.

None of these require special configuration to unlock. They’re all available once you have Claude Code installed.

Table of Contents

1. Why Architecture Comes First

Before you write a single skill or configure a single hook, your folder structure needs to make sense to an AI reading it cold.

Claude Code reads your files to understand your project. If your code is scattered across a layer-first structure (lib/models/, lib/services/, lib/widgets/), Claude has to piece together what each feature does by jumping between folders. It makes mistakes. It creates files in the wrong place. It generates code that doesn't conform to the pattern used in the rest of the app.

The fix is a feature-first structure. Each feature is a self-contained module. Everything Claude needs to understand the transfer flow, for example, lives inside lib/features/transfer/.

lib/
├── core/
│   ├── constants/
│   ├── errors/
│   ├── router/
│   └── theme/
├── features/
│   ├── auth/
│   │   ├── data/
│   │   │   ├── models/         # Freezed models
│   │   │   └── repositories/
│   │   ├── presentation/
│   │   │   ├── screens/
│   │   │   ├── widgets/
│   │   │   └── providers/      # Riverpod providers
│   │   └── auth.dart           # barrel export
│   ├── transfer/
│   │   ├── data/
│   │   ├── presentation/
│   │   └── transfer.dart
│   └── wallet/
│       ├── data/
│       ├── presentation/
│       └── wallet.dart
└── main.dart

This structure tells Claude immediately: "Everything for the transfer feature is in lib/features/transfer/"When you ask it to 'add a beneficiary validation to the transfer flow,' it knows exactly where to look and where to create new files.

It also maps cleanly to Riverpod with code generation. Each feature's providers live close to the screens that use them, which means build_runner output lands in the right place, too.

2. Setting Up Your CLAUDE.md

CLAUDE.md is arguably the most important file in your Claude Code setup. It's loaded at the beginning of every session. It remains in context throughout the conversation, helping Claude stay aligned with your project's architecture, conventions, and development practices no matter how long the session becomes.

Create it at the root of your project:

touch CLAUDE.md

Here's a template shaped for a Flutter/Riverpod project:

# My Flutter App

## Commands
- `flutter pub get` — install dependencies
- `dart run build_runner build --delete-conflicting-outputs` — generate code
- `flutter analyze` — run linter
- `flutter test` — run tests
- `flutter run` — start dev build

## Architecture
Feature-first folder structure. Each feature lives in lib/features/<name>/.
State management: Riverpod with @riverpod code generation (AsyncNotifier pattern).
HTTP: Dio with interceptors in lib/core/network/.
Navigation: GoRouter with named routes defined in lib/core/router/.
Models: Freezed + JsonSerializable. Run build_runner after any model change.

## Conventions
- All monetary amounts in the smallest unit (e.g. kobo for NGN), stored as int — never use doubles for money
- Use ref.invalidate() not ref.refresh()
- No business logic in widgets — all logic goes in notifiers or repositories
- Widget files contain only one public widget per file
- Barrel exports via feature.dart in each feature root
- Prefix private widgets with an underscore

## What NOT to do
- Do not add new packages without asking first
- Do not modify *.g.dart or *.freezed.dart files directly — regenerate with build_runner
- Do not put API calls directly in notifiers — always go through the repository layer

A few things to note about this file:

Keep it honest: If your conventions don't match what's actually in the codebase, Claude will get confused. The CLAUDE.md should reflect how the code actually works today, not aspirationally.

The "What NOT to do" section matters: AI assistants are optimistic. They'll solve the problem in front of them without thinking about side effects. Explicitly telling Claude what to avoid saves a lot of cleanup.

Don't make it too long: Every line in CLAUDE.md costs tokens on every single turn of every session. Put team-wide, always-relevant rules here. Everything else should be a skill (covered next).

3. Feature-First Folder Structure — The Details

Let's look inside a feature in more detail, using a wallet feature as an example:

lib/features/wallet/
├── data/
│   ├── models/
│   │   ├── wallet.dart             # Freezed model
│   │   ├── wallet.freezed.dart     # Generated
│   │   ├── wallet.g.dart           # Generated
│   │   └── transaction.dart
│   └── repositories/
│       ├── wallet_repository.dart  # Abstract class
│       └── wallet_repository_impl.dart
├── presentation/
│   ├── screens/
│   │   ├── wallet_screen.dart
│   │   └── transaction_history_screen.dart
│   ├── widgets/
│   │   ├── balance_card.dart
│   │   └── transaction_tile.dart
│   └── providers/
│       ├── wallet_provider.dart
│       └── wallet_provider.g.dart  # Generated
└── wallet.dart                     # Barrel export

And here's what a clean Riverpod provider looks like in this structure:

// lib/features/wallet/presentation/providers/wallet_provider.dart

import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../data/models/wallet.dart';
import '../../data/repositories/wallet_repository.dart';

part 'wallet_provider.g.dart';

@riverpod
class WalletNotifier extends _$WalletNotifier {
  @override
  Future<Wallet> build() async {
    return ref.watch(walletRepositoryProvider).getWallet();
  }

  Future<void> refreshBalance() async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(
      () => ref.read(walletRepositoryProvider).getWallet(),
    );
  }
}

When Claude Code sees this pattern repeated across multiple features, it learns to replicate it. The more consistent your structure, the better Claude's output matches what you'd write yourself.

4. Writing Skills for Your Most Repeated Tasks

Skills are reusable instruction packs that Claude Code loads when they're relevant. They live in .claude/skills/<name>/SKILL.md and can be invoked manually with /skillname or triggered automatically when Claude recognises the right context.

A simple way to think about a Skill is as a specialist on your team. Imagine working with a designer, a QA engineer, and a security expert. You don't explain their entire job every time you need their help. Each person already knows their responsibilities and follows a defined process.

Skills work the same way. Instead of repeatedly telling Claude how to generate Riverpod providers, write tests, or review security concerns, you package those instructions into a Skill and let Claude load them whenever they're needed.

Think of a Skill as a saved recipe. Instead of writing out the ingredients and cooking steps every time you want to make a meal, you keep the recipe in one place and reuse it whenever needed.

Skills do the same thing for development workflows. They allow you to save a set of instructions once and have Claude follow them consistently every time a similar task comes up.

The key thing to understand is that the description field is what triggers a skill. Claude evaluates it on every turn and decides whether the current task matches. Because of this, you should describe it using the same verbs that developers actually type in real workflows, like build, commit, release, or fix lint, instead of documentation-style language.

Creating Your First Skill

Before you write a skill, think about the tasks you perform over and over again. A good skill captures a workflow you already know by heart. If you find yourself giving Claude the same instructions every session, such as "run flutter analyze, then run build_runner, then execute the tests," that's a good candidate for a skill.

Start with one task. Keep the steps in the exact order you expect Claude to follow, and clearly define what a successful outcome looks like. Don't try to cover every possible edge case. The goal is to automate your normal workflow so Claude can handle the repetitive work consistently, while you step in only when something unexpected happens.

mkdir -p .claude/skills/flutter-release
touch .claude/skills/flutter-release/SKILL.md
---
name: flutter-release
description: |
  Use this skill when building a release APK or preparing the app for deployment.
  Triggers on: "build release", "generate apk", "prepare release", "release build".
allowed-tools: Bash Read
---

# Flutter release checklist

Run these steps in order. Do not skip any step.

1. Run `flutter pub get`
2. Run `dart run build_runner build --delete-conflicting-outputs`
3. Run `flutter analyze` — fix every error before proceeding. Do not continue with warnings treated as errors.
4. Run `flutter test` — if any test fails, fix it before continuing
5. Run `flutter build apk --release`
6. Confirm build output at `build/app/outputs/flutter-apk/app-release.apk`
7. Create a git commit: `chore: release build vX.X.X`

If any step fails, stop and report the error clearly. Do not skip ahead.

Now, whenever you type "prepare a release" or "build the apk"Claude follows this checklist without you having to remind it of the steps.

A Skill For Conventional Commits

mkdir -p .claude/skills/commit
touch .claude/skills/commit/SKILL.md
---
name: commit
description: |
  Use when committing changes or writing a commit message.
  Triggers on: "commit", "git commit", "commit changes", "write a commit message".
---

Follow Conventional Commits format:

Types: feat | fix | chore | refactor | docs | test | perf

Format: `type(scope): short imperative summary`

Rules:
- Subject line max 72 characters
- Imperative mood — "add" not "added", "fix" not "fixed"
- Scope = the feature name (auth, transfer, wallet, cards)

Examples:
- `feat(transfer): add beneficiary validation on amount input`
- `fix(wallet): correct kobo-to-naira display conversion`
- `chore(deps): upgrade riverpod to 2.6.1`

Always run `flutter analyze` before committing. Never commit with lint errors.

Dynamic Context Injection

Skills support a powerful trick: you can inject live shell output directly into the skill body using !`command` syntax. Claude receives the output as part of the skill, not as a separate step.

For example, you could embed something like !git status inside a skill, so Claude always sees the current state of your repository when applying that skill. In a Flutter workflow, you could also use something like !flutter test so the skill dynamically includes the latest test results before Claude suggests fixes or improvements.

---
name: sprint-status
description: |
  Use when asked about current status, what's left to do, or what changed.
---

## Current git status
!`git status --short`

## Uncommitted changes
!`git diff --stat HEAD`

## Recent commits
!`git log --oneline -10`

## Lint status
!`flutter analyze 2>&1 | tail -20`

Review the above and give a concise summary of: what's done, what's broken, and what needs attention before the next commit.

Type /sprint-status and Claude gets a live snapshot of your project state before responding.

5. Using /loop for Self-Correcting Workflows

/loop is a built-in Claude Code skill that reruns a task repeatedly until a condition is met. It's the difference between "fix this lint error" (one shot) and "fix all lint errors" (autonomous loop).

For example, instead of running a one-time prompt like “fix this lint error,” you would use /loop fix lint errors in this Flutter project until there are no warnings left. Claude will then repeatedly check the output, apply fixes, and recheck until the condition is satisfied.

A more realistic Flutter workflow could look like /loop run flutter analyze and fix all reported issues until analysis passes clean. In this case, Claude keeps running analyses, fixing issues, and revalidating until the project reaches a clean state.

It's worthy of note here that a/loop and a Skill solve two different problems, and it helps to think of them like this:

  • A Skill is knowledge.

  • A Loop is behavior over time.

The pattern is always the same: tell Claude what to run, what to check, and when to stop.

Fix Until Clean

/loop
Run flutter analyze.
If there are any errors or warnings, read each one carefully and fix it.
Run flutter analyze again.
Continue until flutter analyze reports zero issues.
Do not move on while there are errors remaining.

TDD Loop

/loop
Run: flutter test --name "WalletNotifier"
If the test fails, read the failure output carefully.
Make the minimal code change required to fix the failure.
Do not change the test itself.
Run the test again.
Stop when the test passes with no errors.

Build a Screen, Check it, Iterate

/loop
Look at the Figma spec notes in CLAUDE.md under "Remaining screens".
Pick the next incomplete screen.
Build the screen following the architecture pattern in lib/features/wallet/presentation/.
After building, run flutter analyze and fix any issues.
Add a comment `// DONE` at the top of the completed screen file.
Move to the next screen.
Stop after completing 3 screens.

A word of caution: /loop is powerful, but give Claude a clear stop condition. "Keep going until it's perfect" is not a stop condition. "Stop when flutter analyze and flutter test both pass with zero issues." is.

6. Subagents for Parallel Screen Development

Subagents are isolated Claude instances that run a task in their own context window and then return only a summary to the main session. This changes how you think about working with Claude Code on a multi-screen project.

A simple way to understand it is to imagine building a full Flutter app with multiple screens. Without subagents, you would design the home screen, then the profile screen, then settings, all in one long conversation. Over time, the context gets heavier, and Claude starts losing focus on earlier decisions.

With subagents, it's like giving each screen to a different engineer. One works on the home screen, another builds the profile screen, and another handles settings. Each one works independently, follows the same project rules, and reports back only when the screen is ready. You then combine their output into the main project without losing clarity or consistency.

Setting Up a Screen-Builder Subagent

Create a file at .claude/agents/screen-builder.md:

---
name: screen-builder
description: Builds a single Flutter screen following the app's feature-first Riverpod architecture
model: claude-sonnet-4-6
tools: [Read, Write, Bash, Glob]
---

You are a Flutter engineer building a screen for a fintech app.

Before building anything:
1. Read lib/features/wallet/presentation/screens/wallet_screen.dart to understand the existing screen pattern
2. Read CLAUDE.md for conventions and architecture rules
3. Read the feature's existing providers in the presentation/providers/ folder

When building the screen:
- Follow the exact same structure as the existing screens
- Use AsyncValue pattern for loading/error/data states
- No business logic in the widget — all state goes through the provider
- Every monetary amount displayed in naira but stored in kobo (divide by 100 for display)
- Use GoRouter for navigation, not Navigator.push

After building:
- Run flutter analyze on the file
- Fix any errors
- Return a summary: file path created, provider used, any decisions made

Using it

In your main session, you can now say:

Use the screen-builder subagent to build the Transaction History screen.
The screen should show a list of transactions from the WalletNotifier provider.
Each item should display: amount (formatted), description, date, and status badge.

Claude dispatches the subagent, which reads your existing code for context, builds the screen following your patterns, fixes any lint errors, and returns a clean summary, without cluttering your main thread with every intermediate step.

You can also run multiple subagents simultaneously for truly parallel work:

Dispatch three screen-builder subagents in parallel:
1. Transaction History screen (list of transactions)
2. Send Money screen (amount input + recipient selection)
3. Wallet Top-Up screen (amount input + payment method)

Each should follow the existing wallet feature patterns.
Report back when all three are complete.

7. Hooks — Enforcing Rules Deterministically

Skills and subagents influence how Claude thinks and plans, but hooks are different. Hooks are deterministic. They run automatically at specific lifecycle events, no matter what Claude decides to do. This makes them useful for enforcing hard rules in your workflow.

A simple way to understand it is to think of hooks as guards in a real engineering pipeline. For example, before any code is committed, a PreToolUse hook can run to check formatting or block unsafe changes. After a tool runs, a PostToolUse hook can validate the output. When a session ends, a Stop hook can trigger cleanup tasks or logging. Other events, like SessionStart, PreCompact help you initialize context or manage memory before Claude continues working.

In practice, hooks are how you enforce consistency. While Skills and subagents guide Claude’s behavior, hooks ensure certain actions always happen at the right moment, without relying on Claude to “remember” or “decide.”

Block Edits to Generated Files

Generated files like *.g.dart and *.freezed.dart should never be edited manually — they get overwritten by build_runner. This hook blocks Claude from writing to them:

Create .claude/hooks.json:

{
  "PreToolUse": [
    {
      "matcher": "Write|Edit",
      "command": "bash -c 'if [[ \"\(CLAUDE_TOOL_INPUT_PATH\" == *.g.dart ]] || [[ \"\)CLAUDE_TOOL_INPUT_PATH\" == *.freezed.dart ]]; then echo \"Blocked: Do not edit generated files. Run build_runner instead.\"; exit 1; fi'"
    }
  ]
}

Run Analyze Before Every Stop

This hook runs flutter analyze before Claude considers its turn complete, catching lint errors before they accumulate:

{
  "Stop": [
    {
      "command": "bash -c 'result=\((flutter analyze 2>&1); if echo \"\)result\" | grep -q \"error •\"; then echo \"Flutter analyze found errors. Fix before stopping:\"; echo \"$result\"; exit 1; fi'"
    }
  ]
}

Now Claude can't finish a turn if there are lint errors. It gets blocked and has to fix them first.

8. Putting It All Together: A Real Sprint Workflow

Here's what a typical feature development session looks like when all of this is configured:

Morning: Check Project State

/sprint-status

Claude reads live Git status, recent commits, and current lint output, then summarises what needs attention.

Start a New Feature

I need to build the beneficiary management feature. 
Users should be able to save, view, and delete beneficiaries for the transfer flow.
Start with the data layer — Freezed model and repository interface.

Claude reads your CLAUDE.md and existing feature patterns, then builds the model and repository in the right place, following your conventions.

Generate All the Screens in Parallel

Use the screen-builder subagent to build:
1. BeneficiaryListScreen — shows saved beneficiaries with search
2. AddBeneficiaryScreen — form with account number and bank selection
3. BeneficiaryDetailScreen — shows details with delete option

Fix Everything Until it's Clean

/loop
Run flutter analyze.
Fix all errors.
Run flutter test.
Fix any test failures.
Stop when both pass with zero issues.

Commit Cleanly

Commit the beneficiary feature

The commit skill triggers, runs analyze one more time, and creates a correctly-formatted conventional commit message.

Key Takeaways

If there's one key takeaway from all of this, it's that Claude Code isn't just about prompting. It's about setup. The quality of its output is shaped far more by what you define about your project upfront than by what you type in the moment.

This is also what separates vibe coding from real AI-assisted engineering. Without structure, you end up guessing and reacting, which feels fast but breaks down quickly.

With the right setup, Claude becomes a pair programming partner that follows your conventions and handles execution while you focus on decisions that actually require engineering judgment. That shift is what lets you spend less time fixing generated code and more time solving the problems that matter.

The payoff compounds. A CLAUDE.md takes 20 minutes to write. A skill for your release flow takes 10 minutes. But both of those pay for themselves the first time Claude correctly follows your process without you having to walk it through every step.

Start small: write your CLAUDE.md this week. Add one skill for the task you repeat most — committing, releasing, or running lint. Then, when you're comfortable, try a /loop on your next test-fixing session. The rest follows naturally.

The goal isn't to let AI write all your code. It's to stop spending your limited engineering time on the parts that don't require your judgment, and to spend more of it on the parts that do.



Read the whole story
alvinashcraft
58 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Claude Code for Infrastructure as Code: A Practical Guide

1 Share
Learn how Claude Code fits Infrastructure as Code work, what it does well, where it needs guardrails, and how to keep production changes safe.
Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories