The Shift to Native Effect-TS Loops Building production-ready AI agents requires absolute control over execution. When the engineering team at OpenGov first launched OG Assist, an embedded AI assistant across their government ERP software, they relied on LangGraph. But scaling changed their requirements. To achieve fine-grained control, they migrated to a custom agent loop written in TypeScript using Effect. This shift allowed the team to inject different language models dynamically using clean dependency injection. Effect provides built-in schemas, error handling, and structured concurrency out of the box. By building a native loop, they gained full agency over execution, making it easier to parse tool calls and hot-swap LLMs without fighting framework abstractions. Managing Mutation with Deterministic Interrupts Safety in enterprise environments is non-negotiable, especially when agents handle municipal workflows like utility billing or asset management. To prevent unauthorized database changes, the platform implements strict boundaries. When an agent triggers a mutating tool call, the system deterministically interrupts the run. It pauses the execution thread and renders a dedicated approval UI. The user must explicitly accept or reject the action. For broader sandboxing, any code execution or file creation occurs in isolated, ephemeral environments that tear down automatically, ensuring complete isolation from production systems. Tackling Context Bloat with Rolling Summaries Long-running conversations quickly degrade model performance and break token limits. Stuffing historical messages into the prompt is a recipe for failure. To solve this, the team implements a rolling summarization strategy. After a set number of turns, the system summarizes the conversation history up to that point. It retains only the most recent messages in raw format, while using the running summary for memory recall. If a user refers to an event from a hundred turns prior, the agent retrieves the context from the summary, preserving accuracy without inflating latency. Native Tracing Eliminates Production Blind Spots You cannot scale what you can't see. Debugging multi-step agent behaviors across microservices is notoriously difficult. By building on top of the Effect ecosystem, the team gets distributed tracing out of the box. Every functional span is tagged automatically. When a tool call slows down or fails, developers can inspect the exact execution path, isolate bottlenecks, and cross-reference data across services. Combining these traces with real-time feedback loops—like automated testing in CI and user-driven thumbs-up metrics—allows the engineering team to deploy updates with confidence.
TypeScript
Programming Languages
Sep 2021 • 1 videos
High activity month for TypeScript. ArjanCodes among the most active voices, with 1 videos across 1 sources.
Jul 2022 • 1 videos
High activity month for TypeScript. ArjanCodes among the most active voices, with 1 videos across 1 sources.
Oct 2022 • 1 videos
High activity month for TypeScript. ArjanCodes among the most active voices, with 1 videos across 1 sources.
Nov 2022 • 1 videos
High activity month for TypeScript. ArjanCodes among the most active voices, with 1 videos across 1 sources.
Apr 2023 • 1 videos
High activity month for TypeScript. ArjanCodes among the most active voices, with 1 videos across 1 sources.
Feb 2025 • 1 videos
High activity month for TypeScript. ArjanCodes among the most active voices, with 1 videos across 1 sources.
Jul 2025 • 1 videos
High activity month for TypeScript. AI Engineer among the most active voices, with 1 videos across 1 sources.
Oct 2025 • 1 videos
High activity month for TypeScript. Mapbox among the most active voices, with 1 videos across 1 sources.
Jan 2026 • 2 videos
High activity month for TypeScript. Laravel Daily and Svelte Society among the most active voices, with 2 videos across 2 sources.
Feb 2026 • 1 videos
High activity month for TypeScript. Svelte Society among the most active voices, with 1 videos across 1 sources.
Jun 2026 • 1 videos
High activity month for TypeScript. AI Engineer among the most active voices, with 1 videos across 1 sources.
- Jun 26, 2026
- Jan 25, 2026
- Oct 27, 2025
- Jul 15, 2025
- Feb 14, 2025
The Shift to Prompt Engineering Getting an AI to spit out a block of code is easy. Getting it to write *good* code that follows your specific architectural standards is where the real challenge lies. As developers, we're moving into an era where our ability to communicate with OpenAI's ChatGPT is just as vital as our understanding of Python syntax. This process, known as prompt engineering, isn't about magic spells; it's about being methodical with your instructions to ensure the output is actually production-ready. Define the Right Context One of the biggest mistakes beginners make is being too vague. If you ask for an email validation function, the AI might return a messy Python script that lacks type hints or follows outdated patterns. You need to explicitly define the "who" and "how." Tell the AI if you're looking for an exercise for a beginner or a robust, production-level function for a senior developer. For instance, when asking for TypeScript examples, the AI naturally includes type annotations that it might omit in Python unless you specifically demand high-quality, modern standards. Leverage Synonyms and Technical Concepts If the first answer feels off, change your vocabulary. There is a significant difference in how an AI responds to "make this better" versus "refactor this using the Single Responsibility Principle." Terms like "low cohesion" or "separation of concerns" trigger different training data within the model, often leading to more sophisticated refactoring suggestions. If you don't like the first result, pivot to a related technical concept to see if the AI offers a cleaner architectural approach. Control the Output Format Stop settling for plain text blocks. You can instruct the AI to format its response in specific ways to make it more readable or integrable. Whether you need a Mermaid diagram for database structures, a table comparing different libraries, or specific Type Hints in your Python code, you have to be the architect. If the output is too long and hits character limits—a common issue with complex regular expressions—ask the AI to condense the logic or break it into modular steps. The Critical Need for Verification Never trust AI code blindly. ChatGPT operates on probability, not logic. It can confidently present a "Bridge Pattern" that is actually just simple inheritance. Because it draws from a massive pool of beginner-level content on the internet, its understanding of intermediate and advanced software design patterns can be shaky. You must verify every response against your own expertise. AI is a reactive tool; if you don't know the right keywords or patterns yourself, you might find yourself stuck in a loop of mediocre suggestions. Treat it as a pair programmer that occasionally needs a stern code review.
Apr 21, 2023Overview of A/B Testing in Software Design Building software without user feedback is like flying blind. You might assume a specific button color or menu layout works best, but until you measure real-world interactions, you are guessing. A/B testing—also known as split testing—solves this by comparing two versions of a variable to see which performs better. To implement this effectively, you need two core components: a mechanism to toggle between variants (Feature Flags) and a system to measure the results (KPI tracking). Integrating these into your code requires a clean design to ensure you aren't hard-coding logic that becomes a maintenance nightmare later. Prerequisites To follow this tutorial, you should be comfortable with: * Python basics (classes, methods, and decorators). * Working with JSON and environment variables. * Basic GUI concepts (the examples use Tkinter). * Familiarity with REST APIs and the `requests` library. Key Libraries & Tools * GrowthBook: An open-source feature flagging and A/B testing platform that provides a remote API to manage feature states. * Mixpanel: A product analytics tool used to track user events and calculate conversion rates. * python-dotenv: Manages sensitive API keys via a `.env` file. * Requests: Handles the HTTP communication with feature flag services. Code Walkthrough: From Local Config to Remote Flags Step 1: Local Feature Flags Start by decoupling your UI from the logic. Instead of hard-coding a button, use a boolean flag. We can read this from a local `config.json` to avoid changing source code for every test. ```python @dataclass class Config: show_save_button: bool = True def read_config_file() -> Config: config_path = Path.cwd() / "config.json" data = json.loads(config_path.read_text()) return Config(**data) ``` Step 2: Integrating GrowthBook Local files don't scale when you have thousands of users. By using GrowthBook, you can toggle features remotely via an API. The logic remains the same in your GUI, but the source of truth moves to the cloud. ```python from growthbook import GrowthBook import requests def read_remote_config() -> Config: api_key = os.getenv("GROWTHBOOK_KEY") resp = requests.get(f"https://cdn.growthbook.io/api/features/{api_key}") features = resp.json()["features"] gb = GrowthBook(features=features) return Config( show_save_button=gb.is_on("show_save_button") ) ``` Step 3: Tracking User Interaction with Mixpanel A/B testing is useless without data. When a user clicks a button in "Variant A," you must report that event. We use Mixpanel to capture these interactions. To keep the design clean, we pass a `post_event` callable to our UI class. ```python from mixpanel import Mixpanel def post_event(event_type: str): mp = Mixpanel(os.getenv("MIXPANEL_TOKEN")) mp.track("user_id_123", event_type) In the GUI Class def on_button_save(self): self.save_logic() self.post_event("save_button_clicked") ``` Syntax Notes and Patterns * **Dependency Injection**: Notice how we pass the `post_event` function into the GUI class. This makes the code testable; you can pass a dummy lambda function during unit tests to avoid hitting the actual Mixpanel API. * **Unpacking Dictionaries**: Using `Config(**data)` is a concise way to map JSON keys directly to data class attributes. * **Lambdas for Defaults**: If a user opts out of tracking, we replace the `post_event` function with a `lambda _: None`. This prevents `AttributeErrors` without needing complex `if/else` checks throughout the UI code. Practical Examples 1. **Phased Rollouts**: Use a feature flag to enable a new search algorithm for only 10% of users to monitor server load before a full release. 2. **UI Simplification**: Test if removing a secondary "Save" button increases the usage of the main menu, potentially decluttering the interface. 3. **Paywall Testing**: Toggle different pricing tiers or trial lengths for different user segments to optimize revenue. Tips & Gotchas * **Privacy First**: Always ask for user consent before tracking data. If you operate in the EU, GDPR compliance is mandatory. Implement a simple opt-in dialog at the first launch. * **Clean Up**: Feature flags are technical debt. Once an experiment concludes and a winner is chosen, remove the conditional logic and the flag from your code to keep the codebase maintainable. * **Timeout Management**: When fetching remote flags, always set a timeout in your `requests.get()` call. You don't want your application to hang indefinitely because a third-party service is down.
Nov 4, 2022Navigating the Concurrency Conundrum: Threading, AsyncIO, and Subprocesses In the modern Python ecosystem, the question of how to handle concurrent operations is no longer a matter of simply spawning threads. The choice between threading, asyncio, and multiprocessing defines the very architecture of an application. While threading remains a foundational tool, it is increasingly viewed as an older variant of concurrency, best reserved for specific worker-thread scenarios where high-frequency interaction with the main execution flow is unnecessary. If your task involves computing analytics in the background once an hour, a worker thread is perfectly adequate. However, for more complex sequences—such as an API that must query a database, perform security checks, and then return a response—threading becomes incredibly cumbersome. The resulting code often becomes bloated and difficult to maintain because managing the lifecycle of a thread for every sequence of actions is architecturally inefficient. This is where asyncio has redefined the landscape. Moving away from the "callback hell" that plagued early JavaScript development, asyncio utilizes the concept of promises and future objects, integrated directly into the language syntax via the `async` and `await` keywords. This allows developers to treat concurrent code as if it were synchronous, maintaining readability while reaping the benefits of non-blocking I/O. It is a more modern approach that handles complex asynchronous operations with significantly less overhead. However, practitioners must remain cognizant of the Global Interpreter Lock (GIL). If true parallel execution is required—meaning the ability to utilize multiple CPU cores simultaneously—the multiprocessing library is the only viable path. Because it spawns entirely new processes handled at the OS level, it bypasses the limitations of the GIL, making it essential for CPU-bound tasks as opposed to the I/O-bound tasks where asyncio shines. The Philosophy of Test-Driven Development and the Coverage Trap Test-Driven Development (TDD) is frequently misunderstood as a rigid academic exercise, but its true value lies in how it shapes the design of the code itself. When you commit to writing tests before implementation, you are forced to define the boundaries and interfaces of your objects clearly. This naturally aligns with high-level design patterns. For instance, if you find that a test is difficult to write because of too many dependencies, it is a signal that your code is too tightly coupled. Instead of hacking together a fragile solution, this is the moment to reach for a Strategy Pattern or a Higher-Order Function. By passing behavior as an argument rather than hardcoding it, you make the unit test trivial and the code more robust. Design patterns should not be an afterthought or something relegated to a dedicated "refactoring phase"; they should emerge as the natural response to making code testable. However, a common pitfall in TDD is the obsession with 100% code coverage. This is often a waste of time and a classic example of the Pareto Principle at play. Reaching that final 20% of coverage frequently requires 80% of the effort because those areas of the code are inherently complex or involve edge cases that are better served by different testing methodologies. High coverage numbers do not necessarily equate to high-quality code. You can write a test that hits every line of a function but fails to assert whether the function actually performs its intended purpose. Instead of chasing a metric, developers should balance their efforts between unit tests, which are excellent for isolated logic, and end-to-end tests, which verify the system as a whole. A pragmatist recognizes that 80% coverage with strong assertions is far more valuable than 100% coverage achieved through low-quality tests written just to satisfy a linter. Bridging the Gap: Solid Principles in a Multi-Paradigm World While the SOLID Principles were birthed in the era of pure Object-Oriented Programming (OOP), their relevance persists even as the industry moves toward functional concepts. Principles like **Single Responsibility** are universal; whether you are writing a class or a function, that unit of code should not span hundreds of lines or attempt to solve three different problems at once. However, some aspects of SOLID do not translate directly to functional programming. The Liskov Substitution Principle, for instance, is deeply rooted in class inheritance. If your architecture relies on functional compositions rather than inheritance hierarchies, searching for a direct SOLID equivalent can be counterproductive. Instead of adhering strictly to OOP dogmas, the modern developer should focus on broader design principles: **low coupling, high cohesion, and the separation of creation from use.** These ideas are paradigm-agnostic. In Python, which is uniquely positioned as a multi-paradigm language, this often means knowing when to use a class and when a simple function will suffice. Object-oriented design was the dominant trend of the 1990s, but it can lead to unnecessary verbosity if overapplied. If a functional approach produces shorter, more readable code that achieves the same result, it is the superior choice. The goal is not to be a purist, but to select the tool—be it a Factory Pattern or a partial function application from the functools library—that minimizes complexity and maximizes maintainability. Professional Growth and the Imposter Syndrome Reality Transitioning through the stages of a software career—from junior to senior—is less about learning more syntax and more about increasing your level of independence and responsibility. A junior developer can write a function given specific instructions, but a senior developer can take a vague problem and architect a system that solves it while remaining resilient to future changes. This growth requires a shift in how you view your own expertise. The imposter syndrome is a near-universal experience in tech, exacerbated by the public nature of modern development. Whether you are publishing an open-source library or undergoing a code review, the feeling of being a "fake" often stems from the fear of criticism. The secret to overcoming this is to divorce your ego from your code. When you receive critical feedback, you aren't being attacked; you are being presented with an opportunity to learn something that will make you a better developer tomorrow. Optimizing for a career path also requires making a choice between chasing the highest salary and chasing the most significant personal growth. While domains like machine learning and data science currently command high pay, the most sustainable strategy is to choose roles that keep you in a "learning position." Skills compound over time. If you optimize for the most complex problems and the smartest teams, your value will eventually far exceed someone who optimized for a high starting salary in a stagnant role. This iterative approach to self-improvement—setting small, realistic goals and focusing on specific projects rather than trying to learn every framework at once—is the only way to avoid the "tutorial hell" that prevents many intermediate developers from ever reaching senior status. Architectural Best Practices: Libraries, Frameworks, and Tools Selecting the right tools is a critical skill that differentiates experienced architects from beginners. In the web development space, frameworks like FastAPI and Next.js have become favorites for their ability to streamline complex tasks like server-side rendering and type-safe API creation. However, there is a recurring temptation among developers to build everything from scratch—a mistake that can consume months of development time with little to no return on investment. Unless your company’s core value proposition is building a new build tool, you should use existing frameworks. They are maintained by communities that have already solved the security, performance, and compatibility issues you haven't even thought of yet. In the Python world specifically, the use of type hints has become a non-negotiable best practice. Type hints are not just for the computer; they are a communication tool for other developers. They force you to think about the shape of your data and the contracts between your functions. When paired with modern editors like VS Code, they provide immediate feedback that prevents an entire class of runtime errors. While Python remains a "consenting adults" language—meaning its dunder methods and dynamic nature allow you to bypass almost any protection—architecting with clear facades and underscores to indicate private internal state remains the best way to manage complexity in large-scale projects. Whether you are managing dependencies with Poetry or deploying containers via Docker, the goal is always the same: reduce the mental overhead required to understand and change the system. Conclusion: The Path Forward The landscape of software development is constantly shifting, with Python 3.11 promising significant performance boosts and new languages like Rust gaining traction for their memory safety. Yet, the core tenets of the craft—writing clean, testable, and decoupled code—remain static. Becoming a better developer is not about finding a magic bullet or a single "perfect" framework. It is about the daily application of boy scout principles: leaving every piece of code a little better than you found it. As you move forward, focus on the projects that challenge you, embrace the criticism that helps you grow, and always prioritize the readability of your code over its cleverness. The future of development belongs to those who can bridge the gap between technical excellence and practical, user-centric design.
Oct 4, 2022The Evolution of a Developer Perspective When we look at the trajectory of a successful software project or a career, we often obsess over the end state. We see the 100,000 subscribers, the five million views, or the robust production application. But the reality of growth is far more chaotic and experimental. My own journey with ArjanCodes didn't begin as a Python channel. It started as a reflection on the mistakes I made while running a startup. I wanted to talk about picking the wrong libraries, choosing the wrong platforms, and making poor architectural decisions. The pivot to technical tutorials happened because the audience responded to the "how" and the "why" of code. It wasn't about being a Python guru; it was about the discipline of software design. I realized that while many people know the syntax of a language, fewer understand how to get from a problem description to something that actually makes sense in code. That process—the translation of logic into maintainable architecture—is the most interesting part of programming. It transcends specific frameworks or the flavor-of-the-month library. It’s about building systems that don't crumble under their own weight the moment you need to change a requirement. The Iteration Mindset in Code and Life One of the most frequent questions I get is about the production quality of my work. People want to know the "secret." There isn't one. The only principle that matters is iteration. In the startup world, we talk about it constantly, but we rarely apply it to our personal development or our coding practices with enough rigor. If you look at my videos from a year ago, I look like a green alien with terrible lighting. I didn't wait until I had a perfect studio to start; I started, noticed a problem, and refused to tolerate it. This is exactly how we should approach software development. You don't have to write perfect code on the first pass. In fact, if you try, you'll likely over-engineer a solution to a problem you don't fully understand yet. Instead, take small steps. Improve the lighting. Tweaking the microphone. Refactor that one function. This persistent, incremental improvement has incredible results over the long term. As Ray Dalio mentions in his book Principles, you must be perceptive enough to notice problems and adamant enough to fix them. Whether it’s a bug in your deployment pipeline or a bad shadow in a video frame, the process of fixing it is what builds expertise. The Paradigm Shift: Functional vs. Object-Oriented There is a long-standing tension in the industry between Object-Oriented Programming (OOP) and Functional Programming. For a long time, we were told that if you’re building "serious" business applications, you must use OOP. This is a rigid way of thinking. Python is unique because it supports both paradigms strongly, and I find myself moving more toward functional approaches every day. Functions often lead to shorter, more readable code because they require less boilerplate. However, classes still have a vital role in representing structured data. If you use Pydantic or data classes, you get validation and type safety that are harder to achieve with just dictionaries or tuples. My rule of thumb is simple: use classes for data and state, but use functions for behavior. If a method in a class starts getting too long, I split it out. I don't care about purity; I care about readability. You aren't marrying a paradigm. You are using tools to solve a problem. If the code is easy for another developer to understand six months from now, you’ve won. If you followed every design pattern but made the code unreadable, you’ve failed. Navigating the Framework Marriage Choosing a framework like Django or React is a major life event for your code. As Robert C. Martin points out, you are essentially marrying the framework. You have to follow its rules, its directory structures, and its philosophy. If you try to fight the stream, you’ll just end up with a mess. For example, if you're in Django, you should do things the "Django way." But that doesn't mean you should let the framework bleed into everything. You still need a layer of your own business logic that is independent. This is why I focus so much on Software Architecture. Whether you're using Node.js or Python, the principles of dependency injection and modularity remain the same. The goal is to make the framework a detail, not the entire story. This becomes critical when you look at deployment. Tools like Docker and Kubernetes have standardized how we think about infrastructure, but even there, simpler is often better. I find myself reaching for AWS Lambda or Google Cloud Run more often because they remove the burden of infrastructure management entirely. Education, Degrees, and the Job Market Is a Master's degree in Computer Science worth it? Years ago, I would have said yes without hesitation. Today, the answer is more nuanced. Universities are in a strange position. Computer science moves so fast that a four-year curriculum is often out of date by the time a student reaches their senior year. Academic environments are naturally geared toward theory, which is great for learning Mathematics, but less effective for learning how to deploy a microservice at scale. Furthermore, the grading system in traditional education can actually hinder learning. Research shows that once you grade someone, they stop being interested in the material and start being interested in the grade. My advice to anyone starting out is to treat your first job as your true education. Bootcamps are excellent for pivoting careers quickly, but if you already have the basics, nothing beats working on a real-world project. Developing your analytical skills—the ability to cut a problem into small pieces—is the only "advanced" skill that actually matters. Syntax is easy; logic is hard. The Motivation of the Lifelong Learner Staying motivated in this field isn't about chasing the highest salary or the trendiest language. It’s about intrinsic curiosity. I've been coding since I was 10 years old, starting on a Commodore 16. Even if I weren't doing this for a career, I’d still be doing it for fun. The beauty of teaching on YouTube is that it forces me to learn in the open. Every time I prepare a video on Infrastructure as Code or a specific library like Pulumi, I have to dive deep into the documentation. I have to defend my choices to a community of 100,000 people. This feedback loop keeps me sharp. It’s a reminder that we are all students. If you view yourself as a guru, you stop growing. If you view yourself as a learner, every critical comment is an opportunity to rethink your design and every new framework is a playground. Focus on the basics, iterate relentlessly, and keep your logic simple. That is the only path to becoming a pro.
Jul 29, 2022The Core Principles of Object Creation Design patterns emerged in an era where Object-Oriented Programming (OOP) reigned supreme. Traditionally, the Factory Pattern relies heavily on classes and inheritance to achieve its goals. At its heart, the pattern serves three vital design principles. First, it separates creation from use. By injecting objects of a specific subtype into an application, the system uses those objects without needing to know their exact implementation. This reduces coupling significantly. Second, it adheres to the **Single Responsibility Principle**. Creating an object and using that object are two distinct tasks that should not live in the same function. Finally, it supports the **Open-Closed Principle**: the system remains open for extension (adding new types of exports) but closed for modification (not changing the existing export logic). While the 90s-style implementation uses Abstract Base Classes (ABCs), modern Python offers more streamlined alternatives. Structural Typing with Protocols One of the most powerful shifts in modern Python is the move from nominal typing to structural typing via Protocols, introduced in Python 3.8. Traditional ABCs require explicit inheritance, creating a rigid vertical hierarchy. Protocols, however, work like interfaces in TypeScript. If an object has the required methods, it satisfies the type. ```python from typing import Protocol class VideoExporter(Protocol): def prepare_export(self, video_data): ... def do_export(self, folder): ... ``` By switching to `Protocol`, you eliminate the need for subclasses to explicitly inherit from a base. This matches Python's "duck typing" philosophy. The code remains clean, and the type checker still catches mismatches. However, note that by abandoning inheritance, you lose the ability to provide default method implementations in a superclass. Using Tuples as Lightweight Containers Often, a "Factory" class is just a glorified container for a few related functions or classes. You can replace entire factory hierarchies with simple Tuples. If you need to group a specific video exporter with a specific audio exporter, a tuple does this efficiently without the overhead of a class structure. ```python Mapping quality keys to tuples of classes FACTORIES = { "low": (H264BPVideoExporter, AACAudioExporter), "high": (H264HiVideoExporter, AACAudioExporter), "master": (LosslessVideoExporter, WavAudioExporter) } ``` You can then destructure these tuples at the point of use. This approach is incredibly fast and memory-efficient. The downside? You must remember the order of elements (e.g., video first, then audio), and adding configuration data to a tuple quickly becomes messy. The Pythonic Sweet Spot: Dataclasses and \_\_call__ For a balance of flexibility and readability, Dataclasses combined with the `__call__` dunder method provide a superior alternative to traditional factory objects. This allows you to treat a class instance like a function, providing a clean API for object construction. ```python from dataclasses import dataclass from typing import Type @dataclass class MediaExporterFactory: video_class: Type[VideoExporter] audio_class: Type[AudioExporter] def __call__(self) -> MediaExporter: return MediaExporter(self.video_class(), self.audio_class()) ``` This structure provides named access to components, preventing the ordering mistakes common with tuples. It maintains the decoupling of the original pattern while utilizing Python's most ergonomic features. While dataclasses are slightly slower and use more memory than tuples, the gain in code clarity is usually worth the trade-off in most application-level code. Choosing between these methods depends on your performance requirements, but moving away from rigid inheritance is almost always a win for maintainability.
Sep 10, 2021