GitHub confirmed a breach last week that exposed around 3,800 internal repositories. The cause wasn’t a zero-day. It was a VS Code extension.

Attackers took over the publisher token for Nx Console, which has about 2.2 million installs. They pushed a malicious JavaScript file to the VS Code Marketplace. It was up for 18 minutes before it got pulled. Because of auto-updates, that was enough time for it to hit developer machines and exfiltrate .env files and other credentials. Most EDR tools didn’t catch it because it was plain JavaScript, not a compiled binary.

Three things made this work:

Auto-updates are on by default. Teams treat them as a convenience and don’t review what’s being pushed.
People trust the “verified” badge and high install counts. Those don’t mean the code is safe.
Developers keep long-lived secrets on their machines. Once the extension ran, it had files to steal.
If you work in banking, fintech, or any environment where a compromised laptop creates real risk, you should change how you handle extensions:

Turn off auto-updates for extensions. Set a policy to wait at least 48 hours before pulling new versions. That gives time for bad updates to be found and revoked.
Only allow extensions from an approved list. Lock this with group policy or MDM so devs can’t install outside it.
Stop storing secrets in .env files on disk. Use tools like 1Password CLI, Doppler, or Vault to fetch secrets when you need them and drop them from memory after.
Update your monitoring. Look for behavior, not just malware signatures. Alert on things like a single machine cloning repos it normally doesn’t touch, or making a lot of unusual API calls.
Attackers go after developer machines because the network perimeter is already tough. If your build tools are compromised, the code you ship is compromised.

Most teams already scan the code they write. Fewer teams lock down the tools they use to write it.

DevSecOps #SupplyChainSecurity #AppSec #Engineering

AI 週報 — 2026-05-15 to 2026-05-22 | 當 IPO 傳聞撞上 27 萬人部署規模

OpenAI 據報最快本週遞交 IPO 申請,同時 Anthropic 宣布 KPMG 逾 27 萬員工全面採用 Claude——兩起事件從不同角度指向同一個結論:AI 商業化正在從「試點實驗」轉向「制度性採納」。工程整合成本與可靠性數據能否通過這個規模的考驗,是接下來真正有意義的問題。

OpenAI 邁向公開發行:從研究實驗室到華爾街

本週最具新聞份量的事件是 OpenAI 可能在短期內遞交 IPO 申請。Forbes 與紐約時報均於 5 月 20 日報導,OpenAI 已著手準備機密文件,最快於週五送件OpenAI Could Confidentially File For IPO As Soon As Friday, Report Says – ForbesOpenAI Prepares to File for an I.P.O. in Coming Weeks – The New York Times。

IPO 申請本身不代表業務成熟度。 申請機制允許公司在公開說明書送件前與機構投資人進行機密協商,時間點的解讀空間很大。真正有意義的問題是:OpenAI 的營收成長曲線是否足以支撐私募市場已經賦予它的估值?

從監管文件角度,IPO 為何在此時有意義?紐約時報同週報導,馬斯克對 OpenAI 的訴訟剛被法院駁回——陪審團一致裁定 OpenAI 贏得這場官司Jury throws out Musk’s lawsuit against OpenAI and Sam Altman – PBSAs OpenAI Celebrates Court Win Against Musk, Other Challenges Lie Ahead – The New York Times。訴訟風險移除後,公開發行的監管障礙降低了一項不確定因素。但 Marcus on AI 同日發布的分析指出,OpenAI 近期多項頭條聲稱存在數學基礎瑕疵,呼籲讀者謹慎對待未經驗證的數據Checking the math behind OpenAI and Anthropic’s latest headlines – Marcus on AI | Substack。在看到實際 S-1 檔案前,對估值相關敘事應保持觀望。

另一個組織訊號:Greg Brockman 已正式接管 OpenAI 產品線Greg Brockman Officially Takes Control of OpenAI’s Products in Latest Shake-Up – WIRED,從技術共同創辦人轉向產品營運角色,這是公司從研究為本轉向商業執行的組織訊號,而非技術訊號。

事件 解讀層次
IPO 申請傳聞 發布(非正式公告)
訴訟被駁回 商業風險降低
Brockman 接管產品 組織重心轉移,非技術發布

結論: IPO 進程是高度確定的方向,但實際價值判斷需等 S-1 數據出爐。營收組成與單位經濟是核心觀察指標,而非上市時程。

Anthropic 企業部署規模式的到來

本週第二個重量級新聞是 Anthropic 與 KPMG 達成戰略聯盟,涵蓋 KPMG 全球逾 27.6 萬名員工KPMG integrates Claude across its core business and workforce of more than 276,000 in strategic alliance – Anthropic。同一週,BMS(必治妥施貴寶)也宣布採用 Claude 加速醫藥研發與全球工作流程BMS taps Anthropic’s Claude for enterprise-wide AI adoption to speed R&D, global workflows – Fierce Pharma。

這個數量級有意義嗎?有,但需要條件約束。 27.6 萬人覆蓋不等於 27.6 萬人每天在生產環境使用 AI。企業新聞稿中的「戰略聯盟」與「全企業部署」往往是分階段目標,而非現狀快照。Anthropic 自己的新聞稿未說明實際月活躍用戶或 API 呼叫量,整合成本的實際規模同樣不在公開資訊中。

四大會計事務所的工作流程高度標準化且涉及大量文件審查、稅務分析、稽核編碼——若 KPMG 內部有可驗證的採用率數據出爐,這將是迄今最接近「制度性採納」定義的案例。但在此之前,聲稱這是規模化成功的證明,是把願景敘事當作事實。

同週稍早,Anthropic 發布 Code with Claude 的 Managed Agents 功能,支援主動式工作流程與能力曲線追蹤Anthropic’s Code with Claude Announces Managed Agents, Proactive Workflows, Capability Curve – infoq.com。「主動式代理」意味著系統根據上下文自動觸發下一步行動,而非等待人類提示。從「工具」到「能動者」的範式轉移成立的前提是:系統在授權範圍內可預測地完成任務。這是可靠性要求最高的部署形態——任何環節失效都會中斷依賴它的工作流鏈。 目前的宣布屬於「發布」層次,企業 API 的錯誤恢復機制與實測數據尚待驗證。

與替代方案比較: Microsoft 同期發布 Work IQ 功能,號稱能追蹤員工與 AI 工具的互動效率How Work IQ is supercharging our AI usage at Microsoft – Microsoft,但其本質是生產力度量工具,不是 AI 能力本身。Anthropic 專注讓 AI 主動行動,Microsoft 專注讓 AI 的行動可被量化——兩家公司正在從不同方向切入同一個問題。

基礎設施更新:NVIDIA CPU 與 Google I/O 代理軍備

NVIDIA Vera:首款專為代理工作負載設計的 CPU

NVIDIA 於 5 月 18 日發布 Vera CPU,據稱是業界首款專為 AI 代理工作負載設計的 CPU,已進入頂級 AI 實驗室部署Vera Arrives: NVIDIA’s First CPU Built for Agents Lands at Top AI Labs – NVIDIA Blog。

評估這則新聞的框架:

這是「發布」還是「可用」?NVIDIA 新聞稿通常在晶片發布時同步宣布合作夥伴,但量產時程與大規模供貨是獨立的變數。Vera 目前鎖定的客戶是「AI 實驗室」,不是一般企業——出貨量在初期受限,價格與供應鏈資訊尚未透明。

對代理工作負載的具體優化方向:多代理協作需要高頻、低延遲的任務調度能力,這與傳統 GPU 加速的訓練或推論場景不同。NVIDIA 若能在 CPU 層面解決任務排程瓶頸,對多代理系統的延遲優化有實質意義——前提是實測資料能驗證這個宣稱,而非僅是行銷文件。

Google Gemini I/O 更新:主動式 24/7 代理

Google 在 I/O 2026 第二天宣布 Gemini 應用程式全面代理化,具備主動式、全天候協助能力The Gemini app becomes more agentic, delivering proactive, 24/7 help – blog.googleI/O 2026: Welcome to the agentic Gemini era – blog.google。「代理化」的具體定義是:Gemini 能根據使用者行為模式主動預判需求並採取行動。

工程落地角度的質疑: 「24/7 主動幫助」在消費者應用裡是合理的功能描述,但在企業場景中,「主動」與「自動執行」之間存在可靠性鴻溝。企業需要的不是「可能會幫我做事」的系統,而是「在授權範圍內可預測地完成任務」的系統。目前的宣布屬於「發布」層次,真正落地需要看到企業 API 的可靠性數據與錯誤恢復機制——這些資料目前不在公開資訊中。

晶片競合動態:Amazon 逼近、NVIDIA 仍居核心

AWS 與 NVIDIA 的關係正在出現戰略裂縫。digitimes 報導,Amazon 自研 AI 晶片漸獲採用,但 NVIDIA 仍是 AWS 資料中心的核心供應商Amazon’s AI chip push gains ground as Nvidia remains central to AWS – digitimes。AIMultiple 的 AI 晶片廠商全景圖顯示,NVIDIA 競爭者名單持續擴張,但多數尚未威脅其訓練與高效能推論的領先地位Top 25+ AI Chip Makers: NVIDIA & Its Competitors – AIMultiple。

供應商 當前態勢
NVIDIA 訓練/HPC 領先,Vera 瞄準代理推理市場
AWS (Trainium/Inferentia) 自研晶片獲内部採用,成本優勢針對特定推論場景
其他競爭者 多數集中在特定垂直場景,尚未規模化威脅 NVIDIA 地位

工程師決策點: 若工作負載是訓練或大規模推論,NVIDIA 生態系仍是最短路徑。若是特定場景的邊緣推論,AWS 自研晶片的成本結構值得評估——不是因為性能超越,而是因為性價比。

一週數據掃描

本文所有事實均可於五分鐘內透過公開來源驗證。沒有引用任何未經驗證的匿名消息或內部文件。

Data Infrastructure in a Digital Exile

The Problem We Were Actually Solving

As a data engineer, I’ve spent years building data infrastructure to support high-growth businesses. But my latest project was different. My company was attempting to sell stock photos to customers in a restricted country, where the usual payment gateways – PayPal, Stripe, Gumroad, and Payhip – just wouldn’t work. At first, it seemed like a straightforward problem, but the more I dug in, the more I realized we were facing a classic case of a platform problem masquerading as a technical challenge.

What We Tried First (And Why It Failed)

The initial approach was to use a workaround: we asked customers to purchase a gift card from a supported country and then manually transfer the funds to our restricted country account. It seemed like a viable solution, but the execution was clunky, and customers got frustrated with the hassle. We also tried integrating with local payment processors, but their APIs were limited, and they charged exorbitant fees. It became clear that our makeshift solution was a Band-Aid on a bullet wound – we needed a more robust and scalable solution.

The Architecture Decision

After several weeks of research and prototyping, we decided to focus on integrating with alternative payment processors that operated in restricted countries. It wasn’t easy, as many of these processors had limited documentation, and their APIs were often custom-built. We had to develop a custom payment gateway aggregator to handle the different payment flows and exchange rates. Our team also had to manually onboard each new payment processor, which added operational complexity. Despite the challenges, we managed to create a robust payment system that supported several local payment processors.

What The Numbers Said After

After going live, we observed a significant increase in transaction volume, but our payment success rate plummeted. It turned out that the manual onboarding process and the complexity of the payment flows resulted in a high rate of failed transactions. We had to reengineer our payment system to include real-time error handling, API monitoring, and automated onboarding. It took several iterations, but we eventually improved our payment success rate to 95% and reduced the average transaction time from 30 minutes to 5 minutes. Our operational costs decreased by 30%, and we could finally focus on building a sustainable business in the restricted country.

What I Would Do Differently

Looking back, I would have prioritized the development of a payment gateway aggregator from the get-go. This would have saved us weeks of time and reduced the operational complexity. I would also have explored more robust payment APIs that could handle multiple payment processors and exchange rates. In hindsight, it’s clear that our initial approach was a technical band-aid, and a more forward-thinking architecture decision would have yielded better results from day one.

The Caretaker Sandbox: Your Portable Offline-First Web Template Deck powered by Gemma

This is a submission for the Gemma 4 Challenge: Build with Gemma 4

What I Built

The Caretaker Sandbox is a lightweight, offline-first design deck and template suite built for web developers. It isn’t a complex, heavy IDE designed to replace your terminal; instead, it is a dedicated utility tool in your pocket for constructing, compiling, and detailing lightweight client templates—such as transaction emails, document signature grids, custom interactive PDFs, forms, or responsive visual component headers.

The project is designed with travel and absolute portability in mind. When you are on an airplane, in a train, or off-grid without stable internet, it acts as a fully self-contained sandboxed editor using a pure-vanilla browser-run state engine complete with auto-indenting, syntax highlighting, undo/redo, local templates, and a live secure iframe preview. Once you hit active reception with connection, it unlocks Gemma 4’s full intelligence, serving as an automated structural caretaker that spawns design elements from simple prompts, auto-heals code from runtime errors, and executes isolated file system actions on the host machine.

The entire environment runs without the baggage of heavy modern frameworks—zero dependency on React, Express, or Vite. It is powered entirely by a tiny pure-Node.js HTTP command-and-control plane and vanilla front-end scripts, making it perfectly tailored to run on local machines, headless servers, or even Termux on your mobile phone!

Demo

Experience the live Caretaker Sandbox:

Code

You can access the full template setup configuration scripts and modular repository engine here:

The entire platform is built with a singular bootstrap configuration script (init.sh or update.sh) that installs dependencies (@google/genai and dotenv), configures pure-ESM settings, and outputs the lightweight sandboxing components on command.

How I Used Gemma 4

Integrating Gemma 4 model intelligence as an automated backend advisor for a secure code-regulation workspace requires prompt precision, layout understanding, and structured schema-aligned outputs.

Which model did I use and why?

For the Caretaker Sandbox, we targeted a multi-tier architectural approach that leverages the distinct power of Gemma 4 models:

  1. Gemma 4 31B Dense (The Main Studio Caretaker): We selected the robust 31B Dense model as our primary structural design adviser. When you are connected to the network, the Sandbox uses the modern, lightning-fast gemini-3.5-flash client to drive the Caretaker’s layout reasoning. The 31B Dense configuration represents the absolute peak of Gemma 4’s developer intelligence, allowing it to translate loose layout descriptions into pristine, well-structured HTML elements, CSS variables, and clean vanilla Javascript widgets without breaking syntax rules.
  2. Gemma 4 E4B / E2B (Edge & Offline Travel Companions): Because this toolbox is designed for offline travel (airplanes, train rides, and remote spots), developers can run lighter lightweight models like Gemma 4 E4B or E2B in their local command-line terminals (e.g. via llama.cpp or Ollama in Termux) while using our sandbox as their visual playground. The E4B model is incredibly fast and highly optimized for on-device code generation, striking a near-perfect balance between small device memory and developer accuracy.

The Gemma Caretaker Core

In our workspace, Gemma’s interactions are structured across three core developer disciplines:

  1. The Cyberpunk Web Template Generator (generateTemplate)
    Gemma receives a natural-language description from the user (e.g., “Create a sleek cyberpunk transaction email layout with dark styling”) and returns structured parameters inside a schema-validated prompt constraints. It outputs exact parameters: { html, css, js }. No markdown code fences block the parser—giving you instant layout rendering.
  2. The Code Caretaker Auto-Healer (fixTemplate)
    If an interaction fails or throwing styling discrepancies occur during runtime inspection, you can supply your instructions alongside the active code and error logs. Gemma acts as a refactoring expert, repairing structural layout anomalies and formatting issues while maintaining existing state.
  3. The FS Caretaker Virtual CRUD Controller (caretakerCrud)
    The model translates language definitions into precise operations (e.g., folder synthesis, local file generation, system deletes). These logical commands are safely executed through our pure-Node server into a protected /generated environment directory.

Key Visual and Functional Highlights

  • Travel-Ready Architecture: Runs smoothly off-grid. Code edits, stylesheet rendering, manual file creations, history navigation, and pre-bundled templates work 100% offline.
  • Pure-Node Zero-Overhead Server: Stripped of Express/Vite overheads, it uses a lightweight http-level file server with zero-lock static asset caches that map files instantly into memory.
  • Custom Client-Side Engine: Features a full advanced state manager that diffs nested structures into structured states, providing effortless undo/redo control. Combined with on-the-fly custom syntax coloring and layout splits, it is custom designed to maximize high-contrast accessibility.

Authored by **Brixton Mavu* (brixtonmavu / brixmavu on DEV.to)*

Visualizing 4 Sorting Algorithms with JavaScript Generators — O(n ) vs O(n log n) in Real Time

Sort Visualizer: dark-background bar chart with 50 bars in shades of blue, yellow (comparing), red (swapping), and green (sorted). Bubble Sort tab is active. Controls show Size = 50, Speed slider, Shuffle and Start buttons. Stats: Comparisons: 312, Swaps: 147.

Sorting visualizers are a genre. This one is different in one way that matters: the algorithm code has zero knowledge of the DOM. Bubble, Insertion, Merge, and Quick Sort are plain generator functions that yield at every comparison and swap. The animation driver calls .next() on a timer and updates bars. Changing “algorithm” means swapping the generator. Nothing else changes.

Sort Visualizer: dark-background bar chart with 50 bars in shades of blue, yellow (comparing), red (swapping), and green (sorted). Bubble Sort tab is active. Controls show Size = 50, Speed slider, Shuffle and Start buttons. Stats: Comparisons: 312, Swaps: 147.

🌐 Demo: https://sen.ltd/portfolio/sort-visualizer/
📦 GitHub: https://github.com/sen-ltd/sort-visualizer

Why generators?

The usual approach: mix the algorithm and the DOM in a single function, using async/await + sleep() to pause.

// the common pattern — tightly coupled
async function bubbleSort(bars) {
  for (let i = 0; i < bars.length - 1; i++) {
    for (let j = 0; j < bars.length - 1 - i; j++) {
      bars[j].style.background = "yellow";     // DOM inside sort logic
      bars[j + 1].style.background = "yellow";
      await sleep(delay);
      if (height(bars[j]) > height(bars[j + 1])) {
        swap(bars[j], bars[j + 1]);
        await sleep(delay);
      }
      bars[j].style.background = "";
      bars[j + 1].style.background = "";
    }
  }
}

Problems:

  • Can’t test the sort logic without a DOM.
  • Changing animation speed or stopping mid-sort requires ugly cancelled flags.
  • Adding a new algorithm means copy-pasting the sleep + color update infrastructure.

The generator approach separates concerns cleanly:

// sorters.js — no DOM, no timers
export function* bubbleSort(arr, colors, stats) {
  const n = arr.length;
  for (let i = 0; i < n - 1; i++) {
    for (let j = 0; j < n - 1 - i; j++) {
      stats.comparisons++;
      colors[j] = colors[j + 1] = "comparing";
      yield;                                    // pause here
      if (arr[j] > arr[j + 1]) {
        stats.swaps++;
        colors[j] = colors[j + 1] = "swapping";
        yield;                                  // pause here
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
      colors[j] = colors[j + 1] = "default";
    }
    colors[n - 1 - i] = "sorted";
  }
  colors[0] = "sorted";
}

The animation driver in index.html:

function startAnimation() {
  const gen = ALGO_MAP[currentAlgo](array, colors, stats);

  function step() {
    const { done } = gen.next();   // advance one yield
    render();                       // paint whatever state we're in
    if (!done) animId = setTimeout(step, delay());
    else finishAnimation();
  }

  animId = setTimeout(step, delay());
}

delay() reads the speed slider at call time — so the user can change speed mid-sort and it takes effect immediately on the next step.

Stopping is trivial: clearTimeout(animId). No cancelled flag, no promises to abort.

What each algorithm yields

Here’s what the color transitions look like per algorithm:

Bubble Sort

Classic two-pointer walk. The n - 1 - i position is guaranteed sorted after pass i.

for each pass i:
  for j from 0 to n-2-i:
    mark [j, j+1] as comparing → yield
    if out of order:
      mark [j, j+1] as swapping → yield → swap
    reset [j, j+1] to default
  mark [n-1-i] as sorted

O(n²) comparisons. O(n²) swaps worst case. Already-sorted input: 0 swaps, but still O(n²) comparisons.

Insertion Sort

Pick each element, shift right to find its place.

export function* insertionSort(arr, colors, stats) {
  const n = arr.length;
  for (let i = 1; i < n; i++) {
    const key = arr[i];
    colors[i] = "comparing";
    yield;
    let j = i - 1;
    while (j >= 0) {
      stats.comparisons++;
      colors[j] = "comparing";
      yield;
      if (arr[j] > key) {
        stats.swaps++;
        colors[j + 1] = "swapping";
        arr[j + 1] = arr[j];      // shift right
        yield;
        colors[j + 1] = "default";
        colors[j] = "default";
        j--;
      } else {
        colors[j] = "sorted";
        break;
      }
    }
    arr[j + 1] = key;
    for (let k = 0; k <= i; k++) colors[k] = "sorted";  // ← important
    yield;
  }
}

Color tracking gotcha: during the shift phase, multiple positions change from their original values. If you mark individual positions “sorted” during the shift (rather than after placing the key), you get positions that are colored “sorted” but don’t yet hold their final value. The fix: re-mark the full sorted prefix [0..i] after each outer iteration.

O(n²) worst case, but O(n) on nearly-sorted input — good for “almost sorted” real-world data.

Merge Sort

Divide-and-conquer with a temp buffer.

function* _merge(arr, colors, stats, l, m, r) {
  const left  = arr.slice(l, m + 1);  // temp copy
  const right = arr.slice(m + 1, r + 1);
  let i = 0, j = 0, k = l;

  while (i < left.length && j < right.length) {
    stats.comparisons++;
    colors[k] = "comparing";
    yield;
    if (left[i] <= right[j]) { arr[k] = left[i++]; }
    else { stats.swaps++; arr[k] = right[j++]; }
    colors[k] = "swapping";
    yield;
    colors[k++] = "default";
  }
  // drain remainder ...
}

Delegating recursion through generators requires yield*:

function* _mergeHelper(arr, colors, stats, l, r) {
  if (l >= r) return;
  const m = (l + r) >> 1;
  yield* _mergeHelper(arr, colors, stats, l, m);      // delegate
  yield* _mergeHelper(arr, colors, stats, m + 1, r);  // delegate
  yield* _merge(arr, colors, stats, l, m, r);         // delegate
}

yield* propagates each yield up the call chain — the outer animation driver receives them all as if they came from one flat generator.

O(n log n) always. Extra O(n) space for the temp arrays.

Quick Sort

Lomuto partition scheme: pick the last element as pivot.

function* _partition(arr, colors, stats, low, high) {
  const pivot = arr[high];
  colors[high] = "pivot";   // orange color signals the pivot
  let i = low - 1;

  for (let j = low; j < high; j++) {
    stats.comparisons++;
    colors[j] = "comparing";
    yield;
    if (arr[j] <= pivot) {
      i++;
      if (i !== j) {
        stats.swaps++;
        colors[i] = colors[j] = "swapping";
        [arr[i], arr[j]] = [arr[j], arr[i]];
        yield;
        colors[i] = "default";
      }
    }
    colors[j] = "default";
  }
  // place pivot at i+1
  // ...
  return i + 1;
}

The pivot color (orange) makes it visually obvious which element is partitioning the array.

O(n log n) average, O(n²) worst case (already-sorted or all-equal input with Lomuto). Each iteration n - 1 - i elements are guaranteed sorted, so we mark colors[pivotPos] = "sorted" immediately after partition.

Testing without a DOM

Because generators are pure functions over plain arrays, the test suite is straightforward:

import { bubbleSort, insertionSort, mergeSort, quickSort, runSort } from "../sorters.js";

function run(algo, input) {
  const arr    = [...input];
  const colors = new Array(arr.length).fill("default");
  const stats  = { comparisons: 0, swaps: 0 };
  runSort(algo(arr, colors, stats));
  return { arr, colors, stats };
}

test("bubbleSort sorts a random array", () => {
  const { arr } = run(bubbleSort, [5, 3, 8, 1, 9, 2, 7, 4, 6]);
  assert.deepEqual(arr, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
});

test("all bars end as sorted", () => {
  for (const algo of [bubbleSort, insertionSort, mergeSort, quickSort]) {
    const { colors } = run(algo, [3, 1, 4, 1, 5, 9, 2, 6]);
    assert.ok(colors.every(c => c === "sorted"));
  }
});

runSort just drains the generator:

export function runSort(gen) {
  while (!gen.next().done) {}
}

46 tests, zero mocking, no jsdom.

What you can see in the visualizer

At speed 1 (slowest), watch:

  • Bubble: the sorted green grows from the right one element per pass.
  • Insertion: the sorted green grows from the left as each key finds its place.
  • Merge: wide orange bands appear at the merge boundaries; yellow comparisons happen within small segments.
  • Quick: an orange pivot jumps around; after partition, its final position lights up green and never moves again.

At speed 5 (fastest), Bubble on 120 elements becomes a blur while Merge and Quick finish in a fraction of the time — a direct visual comparison of O(n²) vs O(n log n).

TL;DR

  • Generator + yield separates algorithm logic from animation — no DOM in the sort code, no sort code in the animation driver.
  • yield* delegates yields through recursive calls, keeping the driver simple.
  • Speed changes take effect immediately because delay() reads the slider on each setTimeout callback.
  • Stop is clearTimeout — no cancellation flags needed.
  • Color tracking in insertion sort requires re-marking the full sorted prefix after each placement, not individual positions during the shift.
  • Tests run in Node without any browser or mock DOM.

Source: https://github.com/sen-ltd/sort-visualizer — MIT, ~200 lines of JS (sorters) + ~150 lines of UI, 46 unit tests, zero dependencies, no build step.

🛠 Built by SEN LLC as part of an ongoing series of small, focused developer tools. Browse the full portfolio for more.

Day 14 — 184 reader, 3:21 avg read time, 0 new followers, 0 sales

I finally opened the dev.to analytics tab. Here’s what 7 days of writing actually looks like.

Metric (last 7 days) Number
Readers 184
Average read time 3:21
Reactions 1 (from 1 unique user)
Comments 2
Bookmarks 0
New followers 0
dev.to internal algorithm traffic 2 views
bing.com SEO traffic 30 views
External referrers 152 views

184 readers spending 3 minutes 21 seconds each is not invisibility. That’s roughly 10 hours of total attention spent on what I wrote. It’s also basically 0 conversion: no follow, no bookmark, no sale traced back to dev.to.

What I expected vs what happened

I assumed the bottleneck was discoverability. Build more articles, broaden tags, cross-post to Hashnode and Reddit, hope the algorithm picks one up. The data says discoverability isn’t the bottleneck. dev.to’s internal feed sent me 2 visits in 7 days — the algorithm has made up its mind. The 184 readers are coming from somewhere else entirely:

  • 152 external referrers — GitHub README links, gists, GitHub Topics page entries, Apify Store listing
  • 30 bing.com — SEO indexing on specific technical phrases (“refresh-token-only OAuth Apify Actor”, “per-feature KVS quota”)
  • 2 dev.to internal — basically nothing

That means the writing is doing its job as a credibility surface (someone lands on the GitHub repo, sees a dev.to series with 16 build-log posts, decides this is real). It is not doing its job as a discovery surface (dev.to’s algorithm does not push my posts into anyone’s feed).

The piece I had wrong

I have been pricing my time as if dev.to would compound — write 13 articles, the 14th gets distributed, the 15th gets distributed, snowball. The data says no. Each article is a one-shot inbound to whoever already knew about the repo. The 184 readers are not 184 new prospects; they’re 184 visits from the same overlapping pool of GitHub visitors clicking through to read.

Which means the question I should have been asking earlier is: how does the GitHub repo itself get discovered by my actual target reader? Not “how do I get more dev.to algorithm love.” Different problem, different funnel, different lever.

What changes

Reduces dev.to publishing cadence to a respectable signal-pace — write when there’s something to say, not every day. The output for the dev.to surface to date:

  • 13 articles published
  • 1 GitHub star earned (thanks again @kuerdy)
  • 1 reaction, 2 comments, 0 followers

That’s the calibration. If 13 articles + 184 weekly readers translate to 0 followers added, I would be lying to myself to keep going at one-a-day pace. Day 14 might be the last daily entry in this series; the next post comes when there’s a real signal change to report.

What does change behavior

The 6-hour CSV measurement loop keeps running. The Apify Store keyword rank (anonymous, no token-auth personalization trap) is the upstream metric that actually predicts whether a real visitor finds the product. That number is what I should have been watching from day 1, not “did I publish today.”

Raw data

Every shipped surface, every engagement number, every audit finding from the past 13 days in one gist:
https://gist.github.com/foxck016077/18621168173229819e367fa71a6144ab

The Actor itself: https://apify.com/foxck/gmail-inbox-intel (free, MIT, build 0.1.36).

Cohort note: u/tino8383 on Indie Hackers just posted the same shape — 84 visitors, 0 sales, 3 weeks. There’s a recurring pattern across solo launches: the surfaces that work as credibility don’t work as discovery, and the metric that’s easy to measure (visits) doesn’t predict the metric that pays (follow / save / buy). If you’re in this pattern, the thread is worth reading — the comments unpack the trap from a few angles.

I Created an Azure User, Gave It Global Admin Access, Revoked It, and Tested Azure IAM — Here’s What I Learned

As part of my Cloud Engineering and DevOps learning journey, I recently decided to go beyond theory and start practicing Identity and Access Management (IAM) directly inside Microsoft Azure.

I wanted to understand how organizations manage users, permissions, administrative access, and security in real cloud environments. Instead of just reading about Microsoft Entra ID and RBAC, I performed a complete hands-on lab from start to finish.

In this lab, I:

  • created a new Azure user
  • configured Microsoft Authenticator
  • assigned administrative roles
  • logged into the new account using an incognito browser
  • created another user using delegated access
  • revoked the permissions
  • tested whether the account could still perform administrative actions

This exercise gave me a much deeper understanding of how cloud identity management works in enterprise environments.

The screenshots attached throughout this post document the entire process step by step.

Starting From the Default Directory

I began by logging into the Azure Portal using my main administrator account inside the default directory.

From there, I navigated to:

Microsoft Entra ID → Users → All Users

This section contains every identity managed inside the Azure environment, including:

  • users
  • groups
  • applications
  • permissions

At this point, I already had administrative privileges, which allowed me to create and manage other users.

One thing I’m beginning to understand about cloud platforms is that structure and organization are extremely important. Without proper identity management, large organizations would struggle to control access securely.

Creating the “Immortel” User

The next step was creating a new user account named “Immortel.”

During the setup process, I configured:

  • display name
  • username
  • password settings
  • account information

After creating the account successfully, the new user appeared immediately inside the Azure users list.

This helped me understand how organizations onboard employees into Microsoft Entra ID environments.

At this stage, the account existed but had limited permissions.

Logging Into the Immortel Account Using Incognito Mode

Instead of signing out from my main administrator account, I opened a new incognito browser window and logged into the Immortel account separately.

This allowed me to:

  • remain logged into my administrator account
  • test the Immortel account independently
  • simulate how multiple administrators may work simultaneously

Using incognito mode was actually very useful because it helped me manage both accounts side by side without interrupting my existing Azure session.

At this stage, the Immortel account still had very limited access because no elevated administrative roles had been assigned yet.

This demonstrated an important cloud security principle known as:

Least Privilege Access

This means users should only receive the minimum permissions necessary for their tasks.

Setting Up Microsoft Authenticator (MFA)

While logging into the Immortel account, Azure required Multi-Factor Authentication (MFA) setup using Microsoft Authenticator.

This process involved:

  • scanning a QR code
  • linking the account to the Authenticator app
  • approving sign-in requests

This was one of the most practical parts of the exercise because it showed me how organizations secure user accounts beyond just passwords.

I now understand why MFA is considered one of the most important security practices in cloud computing. Even if a password gets compromised, unauthorized users still cannot access the account without the additional verification step.

Assigning Global Administrator and Global Reader Roles

After setting up the Immortel account successfully, I returned to my original administrator session inside the default directory and assigned two important roles:

  • Global Administrator
  • Global Reader

This introduced me to Azure Role-Based Access Control (RBAC).

Before this exercise, RBAC sounded theoretical while reading documentation. But seeing it work practically made everything much clearer.

Understanding the Global Administrator Role

Global Administrator is one of the highest privileged roles inside Microsoft Entra ID.

A user with this role can:

  • create and manage users
  • assign permissions
  • manage identity settings
  • configure security
  • reset passwords
  • control administrative settings

In simple terms, this role has broad control over the Azure identity environment.

Understanding the Global Reader Role

Global Reader works differently.

Users with this role can:

  • view configurations
  • monitor environments
  • review settings

but they cannot make administrative changes.

This role is useful for:

  • auditors
  • compliance teams
  • monitoring personnel

who need visibility without modification access.

Logging Back Into the Immortel Session

After assigning the roles, I refreshed and continued working inside the incognito browser session already logged into the Immortel account.

The difference was immediately noticeable.

The account now had elevated privileges and could access administrative capabilities that were unavailable earlier.

This demonstrated how permissions in Azure are controlled dynamically through assigned roles.

It also showed me how powerful RBAC is in enterprise cloud environments.

Creating Another User Named “Dare”

To confirm the permissions were working correctly, I used the Immortel account to create another user named “Dare.”

The creation process was successful.

This confirmed that the Global Administrator privileges assigned earlier were active and functioning properly.

At this point, I fully understood how delegated administration works in Azure environments.

Organizations can temporarily assign elevated permissions to specific users without permanently granting full administrative control to everyone.

Revoking the Administrative Access

After completing the delegated administration test, I returned to my original administrator session inside the default directory.

I then removed:

  • Global Administrator access
  • Global Reader access

from the Immortel account.

This part of the exercise taught me another important IAM concept:

Permissions can be granted and revoked at any time.

This is critical in real-world environments for situations such as:

  • employee resignation
  • temporary contractors
  • security incidents
  • role changes
  • access reviews

Testing the Revoked Account

To confirm the permissions were truly removed, I returned to the incognito browser session using the Immortel account and attempted to create another user named “John.”

This time, the operation failed.

The account no longer had sufficient privileges to perform administrative tasks.

This final test clearly demonstrated how Azure immediately enforces permission changes once access is revoked.

It was satisfying seeing the entire IAM workflow function exactly as expected.

What This Lab Taught Me

This hands-on exercise helped me better understand:

  • Microsoft Entra ID
  • Identity and Access Management (IAM)
  • RBAC
  • Multi-Factor Authentication (MFA)
  • delegated administration
  • access control
  • permission revocation
  • cloud security principles

More importantly, it helped me realize that identity management is one of the most critical aspects of cloud security.

Without proper IAM controls:

  • unauthorized access becomes easier
  • security risks increase
  • governance becomes difficult
  • compliance becomes harder to maintain

Final Thoughts

This was one of the most valuable Azure labs I’ve completed so far because it moved beyond theory into practical identity administration and security management.

As I continue learning Cloud Engineering and DevOps, I’m beginning to appreciate how important hands-on practice is. Reading documentation is useful, but actually performing these tasks inside a real cloud environment creates a completely different level of understanding.

I still have a long way to go, but each lab continues to build my confidence and understanding step by step.

I’ll continue documenting my learning journey, projects, and hands-on experiences as I grow in cloud engineering and DevOps.
















Why Your AI Workflows Break at Scale—And How to Build Systems That Don’t

Why Your AI Workflows Break at Scale—And How to Build Systems That Don’t

You optimized your AI workflows perfectly—until you scaled them, and suddenly everything broke in ways you never predicted.

I’ve watched this happen to three different SaaS founders in the past eight months. One built a beautiful customer onboarding pipeline in Zapier—GPT-4 for personalization, Airtable for state tracking, Slack for notifications. It worked flawlessly at 50 customers per month. At 300 customers, it started dropping records. At 800, it became a liability that cost them 40 hours of manual cleanup every week.

The AI didn’t fail. The architecture did.

This is automation debt: systems that appear robust at small scale but reveal catastrophic structural weaknesses under real-world load. And unlike technical debt in code, automation debt is invisible until it collapses—loudly, publicly, and usually at the worst possible moment.

The Breaking Point: What Real Failure Actually Looks Like

Most automation post-mortems blame the tool. The API rate-limited us. Zapier dropped the webhook. OpenAI had an outage.

These are symptoms, not causes.

In 2023, a logistics startup I consulted for built a document processing pipeline: emails arrived, GPT-4 extracted shipping data, Make (formerly Integromat) routed it to their TMS, and a Notion database served as the system of record. When they ran 20 shipments per day, everything worked. When they acquired a client that pushed them to 200 shipments daily, the pipeline started producing what their ops team called “ghost records”—shipments that existed in some systems but not others.

The root cause wasn’t any single tool. It was that they had no transactional integrity across the stack. When Make’s webhook to Notion failed—which happened 2-3% of the time—there was no retry logic, no dead-letter queue, no alerting. At 20 records a day, losing 1 record every two days was annoying. At 200 records, losing 4-6 records daily was operationally catastrophic.

A 2-3% failure rate sounds acceptable until you multiply it by volume.

Another case: a marketing agency built an AI content pipeline where n8n called Claude for drafts, then Webflow’s API published them, then HubSpot logged the content event. This three-hop chain worked until Anthropic changed Claude’s output format in a model update. The JSON parsing broke silently—no errors thrown, just empty content fields getting published to their client’s website. They discovered it three days later when a client called.

The system had no validation layer. It trusted the AI completely.

Three Silent Killers of Automation Systems

1. Dependency Fragility

Every external API call in your workflow is a potential failure point. Most automation builders treat APIs as reliable utilities—like electricity. They’re not. They’re more like contractors: occasionally unavailable, prone to changing their prices, and capable of silently changing how they deliver their work.

The average mid-complexity automation stack I audit has 6-8 external API dependencies. Each one has its own rate limits, authentication schemes, versioning policies, and uptime SLAs. OpenAI’s API has experienced 47 documented incidents in the past 12 months alone. Airtable has had multiple periods where their API returned 429 errors at rates that crushed Zapier-based workflows.

The fragility compounds because most tools connect in series, not parallel. If step 4 of a 7-step Zap fails, steps 5 through 7 never execute. And the data from steps 1 through 3? It’s in a limbo state that requires manual intervention to resolve.

2. State Management Chaos

Ask yourself: if your automation workflow stops halfway through, where does the data live, and how do you recover?

Most people can’t answer that question. Because most automation tools don’t have a real answer either.

When you use Zapier or Make, the “state” of a running workflow exists in their proprietary systems. You don’t control it. You can’t query it programmatically. You can’t write recovery logic against it. If a workflow fails at step 6 of 8, your only option is usually to re-run the entire thing and hope the upstream systems are idempotent enough to handle duplicate operations.

Spoiler: they usually aren’t. Sending a customer a welcome email twice because your automation retried is embarrassing. Charging their card twice because your payment automation retried without idempotency checks is a legal problem.

3. The Versioning Problem

This one is insidious because it hits you months after you build, when you’ve forgotten the architectural decisions you made.

AI models update. Prompt outputs change. Tools deprecate features. Anthropic released Claude 3.5 Sonnet and the output structure changed subtly enough that downstream parsers broke across dozens of reported community workflows. OpenAI deprecated several fine-tuned model versions with 30-day notices.

If you’ve built a workflow that parses AI output with a rigid regex or a hardcoded JSON schema, you’ve created a time bomb. The question isn’t if the model output will change—it’s when, and whether your system will fail loudly or silently when it does.

Loudly is better. Silent failures are the ones that destroy customer trust.

Architectural Patterns That Prevent Collapse

Here’s the counterintuitive reality: more resilient automation is often simpler automation. The instinct when building is to chain capabilities together. The engineering discipline is knowing when to break those chains.

Pattern 1: The Saga Pattern for Multi-Step Workflows

Borrowed from distributed systems engineering, the Saga pattern treats each step in a workflow as an independent transaction with a corresponding compensating action. If step 5 fails, you don’t just stop—you execute the compensation logic for steps 4, 3, 2, and 1 to restore a known good state.

In practice, this means building your workflows in n8n or Temporal with explicit rollback logic. When a HubSpot record creation fails after you’ve already sent a welcome email, your compensation action logs the failed HubSpot write to a recovery queue and flags the contact for manual reconciliation—rather than leaving orphaned data across systems.

This isn’t glamorous engineering. It’s the kind of thing that looks like over-engineering until 3am when you’re not paged because your system recovered itself.

Pattern 2: Graceful Degradation Over All-or-Nothing Execution

Build your automations to do something useful even when components fail.

An AI-powered support ticket routing system I helped rebuild had a simple degradation hierarchy: first, it tried GPT-4 to classify and route tickets intelligently. If the OpenAI API was unavailable, it fell back to a keyword-matching classifier. If that failed, it routed everything to a general queue with a high-priority flag for human review.

At full capability, the system routed 89% of tickets correctly without human intervention. During an OpenAI outage, that dropped to 71%—but the system kept running. Before we built the degradation layer, an OpenAI outage meant zero automated routing and a flooded support queue.

Design for the degraded state first. Then add intelligence on top.

Pattern 3: Monitoring-First Design

Most automation builders add monitoring after they notice something breaking. This is backwards.

Before you write the first step of a workflow, define: what does healthy execution look like? What are the measurable signals that something is wrong? How will you know if the AI output quality has degraded even if the workflow “succeeds”?

In Datadog, you can set up synthetic monitors that run your automation on test payloads every 15 minutes and verify output quality—not just workflow completion. I run one that sends a known customer inquiry through our support automation and checks that the AI response contains specific expected elements. If it fails, I know before a customer notices.

Tools like Inngest, Temporal, and even n8n’s enterprise tier have built-in observability. Use them. A $29/month monitoring setup that catches a silent failure before it affects 500 customers is the best ROI in your entire stack.

Building Your Resilience Audit

Before you rebuild anything, you need to know where your debt actually lives. Run this audit on your three most business-critical automations.

Step 1: Map every external dependency. List every API call, every webhook, every third-party service. For each one, write down: what’s the rate limit? What’s the documented uptime SLA? What version of the API are you using? When did they last have a breaking change?

If you can’t answer those questions, that’s your first debt indicator.

Step 2: Kill the workflow manually at each step. Actually disable each step in sequence and document what state the data is in. Is it in a recoverable state? Can you re-run from that point, or do you have to start over? If starting over has side effects—duplicate emails, duplicate records, double charges—you have a state management problem.

Step 3: Run a degradation simulation. Set your OpenAI API key to invalid and run the workflow. What happens? Does it fail with a clear error? Does it fail silently? Does it produce bad output that propagates through the rest of the pipeline? The answer tells you how robust your error handling is.

Step 4: Version audit your AI prompts. When did you last test your prompts against the current version of the model you’re using? If your prompts were written for GPT-3.5 and you’re now on GPT-4o, or vice versa, there’s a good chance output structure has drifted. Run your full test suite against current model outputs—not the outputs you captured six months ago.

One operations leader I worked with ran this audit and found that 4 of her 11 critical automations had what she called “zombie dependencies”—API connections to services that had been deprecated or significantly changed, which her workflows were still calling and silently receiving empty responses from.

The Future-Proof Automation Stack

Tool choice matters less than architectural discipline, but tool choice still matters.

For orchestration, I recommend moving high-value workflows off no-code platforms and onto Temporal or Inngest. Yes, this requires code. Yes, it’s more setup. But you get real state management, built-in retry logic with exponential backoff, and workflow history that you own and can query. For teams that need no-code, n8n self-hosted is the most resilient option in the visual automation space because your data and workflow state live in your infrastructure.

For AI calls, build an abstraction layer—even a simple one. Don’t call OpenAI directly from your automation tool. Call a wrapper function you control that handles retry logic, logs every request and response, validates output structure before returning it, and can swap models without touching downstream logic. When Claude updates or GPT pricing changes, you change one file, not 15 workflows.

For state, use a database you control. Airtable is a product, not infrastructure. Notion is a product, not infrastructure. PostgreSQL on Supabase costs $25/month and gives you ACID transactions, proper querying, and data you actually own. Route all workflow state through it.

For monitoring, Datadog or Better Uptime with custom checks on your critical paths. Set up alerts not just for failures but for success rate drops—if your workflow is succeeding 95% of the time and drops to 87%, that’s a warning signal that precedes a full failure.

The tools that will abandon you mid-workflow are the ones with venture money, uncertain business models, and no clear path to profitability. The tools that won’t are the ones that have been boring and reliable for years. Boring infrastructure is underrated.

Your One Next Step

Don’t try to fix everything at once. Automation debt accumulated gradually and it needs to be paid down the same way.

This week, run Step 2 of the resilience audit on your single most critical automation—the one where a failure would immediately affect revenue or customers. Kill it at each step manually and document what happens to your data. Write down every place where the answer is “I’d have to start over” or “I’m not sure.”

That document is your automation debt ledger. It tells you exactly where to invest your next engineering hour.

The systems that survive scaling aren’t the cleverest ones. They’re the ones built by people who assumed failure was inevitable and designed accordingly.

Follow for more practical AI and productivity content.

Best AI interview prep tools in 2026: 10 compared

Cross-post of an article originally published on the Four-Leaf blog. Canonical: https://four-leaf.ai/blog/best-ai-interview-prep-tools

You search “AI interview prep” and get 40 tools that all claim to be the best. Half do the same thing. A few cost more per month than your grocery bill. Some are genuinely useful. Others are a ChatGPT wrapper with a nice landing page.

We compared ten of the most popular AI interview prep tools. This is what we found: what each one actually does, where it’s strong, where it falls short, and what it costs. No affiliate links. No sponsored placements. Just an honest look at what’s out there.

Quick picks

If you just want the answer:

  • Best all-in-one platform: Four-Leaf at $20/month. Covers interviews, resume, cover letters, job search, and salary negotiation in one product. Lowest total cost for full-pipeline coverage.
  • Best interview-only AI: Final Round AI at $150/month month-to-month (or $25/month billed yearly). The most established dedicated interview simulator.
  • Best for tech and PM roles: Exponent. Strongest structured courses plus peer mock interviews for software engineering, product management, and data science.
  • Best free starter: Google Interview Warmup. No signup, no commitment, good for your first reps.
  • Best for students: Big Interview if your university offers it free. Otherwise too expensive at retail.

The rest of this post breaks each one down in detail, grouped by scope. All-in-one platforms first, then specialists.

What actually matters in an AI prep tool

Before the list, here’s what we evaluated:

What it actually does. Some tools focus on mock interviews only. Others cover resumes, cover letters, job search, and negotiation. Knowing the scope matters because most job seekers need help with more than one thing.

Quality of feedback. There’s a big difference between “good job!” and “your second answer lacked a specific metric to quantify the impact.” We looked for tools that give specific, actionable feedback you can apply to your next attempt.

Price relative to value. A $99/month tool that does one thing well isn’t automatically better than a $20/month tool that does seven things well. We noted the real prices, not the marketing-page prices.

Who it’s built for. Some tools are designed for software engineers. Others target MBAs. A few try to serve everyone. The best fit depends on your situation.

Final Round AI

The most visible name in AI interview prep. Final Round AI built its reputation on mock interviews, and the product shows it. Their interview simulation is sophisticated. Real-time AI feedback, adaptive follow-up questions, video-based practice, and support for technical and behavioral rounds.

They also have resume and cover letter features, but the interviews are the main event. The question database is large (they claim 10,000+) and covers roles from software engineering to consulting. The AI copilot feature that provides real-time guidance is their headline product.

Pricing is tiered and heavily favors annual commitment. Per finalroundai.com/subscription-simple, there is a Free Plan, a Yearly Plan at $25/month billed yearly, a Premium MAX Plan at $41.67/month billed yearly, a Quarterly Plan at $83.33/month billed quarterly, and a Monthly Plan at $150/month for anyone who wants month-to-month flexibility. If interview practice is your primary need and you can commit to a year, the Yearly Plan is competitive. If you want to stay month-to-month, $150/month is a serious investment for someone between roles.

Price: $150/month (Monthly Plan, no commitment); from $25/month billed yearly on annual plans
Best for: Candidates focused primarily on interview practice who want the most established tool

Four-Leaf

Four-Leaf is an AI job search assistant. Voice-enabled mock interviews across 20+ role types (software engineering, data science, product management, consulting, finance, and more), resume builder with ATS match scoring, cover letter generator, AI job discovery, salary negotiation coach, email assistant, and LinkedIn profile optimizer. All seven features included at one price.

The interview practice uses voice-based AI that asks follow-up questions and scores responses on content quality, structure, specificity, and clarity. Speaking out loud under time pressure is a different skill than typing polished answers, and most tools on this list skip it. The resume builder analyzes your resume against a specific job description and shows a before/after match score. Job discovery searches across major boards and ranks openings by skill overlap with your profile.

The tradeoff is maturity. Four-Leaf is newer than some tools on this list. If you want the most established name in interview-only prep, that’s Final Round AI. If you want the broadest feature set at the lowest price, this is the pitch.

(Disclosure: this is our product. We included it because leaving it out of a comparison we wrote would be weirder than putting it in. Same format as every other entry.)

Price: $20/month (3-day free trial, all features included)
Best for: Job seekers who want one tool covering interviews, resumes, cover letters, job search, and negotiation

Big Interview

Big Interview has been around for years. It combines video lessons from a former hiring manager with AI-powered mock interviews. You watch structured training modules, then practice with simulated interviews that give feedback on your answers.

The question library is solid, covering behavioral, situational, and industry-specific categories. The training content is well-produced and genuinely educational. Their newer “PracticeAI” feature uses your resume or job description for personalization, which is a nice touch.

The catch is the price. Individual plans run around $79/month. Many universities and career centers have institutional licenses, which makes it free for students. If your school offers it, use it. At full retail, the value proposition gets harder to justify when cheaper alternatives exist.

Price: ~$79/month (often free through universities)
Best for: Students who get it free through their school’s career center

Career.io

Part of the JEEV/Bold ecosystem, Career.io bundles resume building, interview prep, and career coaching into one platform. Student plans start around $79. Interview-specific access is $24.95/month, which is more affordable than the full suite.

The breadth is there. Resume builder, cover letters, mock interviews, career assessments. Quality is uneven across features, though. The interview practice is competent but not as refined as tools that focus exclusively on interviews. The resume builder is solid. The coaching content varies.

Career.io fits people who want a mid-range all-in-one option and don’t mind trading best-in-class depth for reasonable breadth.

Price: $24.95 to $79/month depending on plan
Best for: Mid-career professionals who want decent coverage across multiple tools

Exponent

Exponent (formerly Pramp) focuses on product management, software engineering, and data science interviews specifically. The platform combines AI practice with peer mock interviews, video courses, and structured study paths. Their question database pulls from real interviews at major tech companies.

The course content is strong. If you’re preparing for a PM interview at a tech company, Exponent’s frameworks and example answers are some of the best available. The peer mock interview matching is a unique feature that no other tool on this list offers.

Exponent is less of an AI tool and more of a structured learning platform with AI components. If you’re targeting tech and PM roles specifically and want courses alongside practice, it’s a strong choice. For general interview prep, it’s too narrow.

Price: $99/month (or annual plans)
Best for: Tech and PM candidates who want structured courses with peer practice

Teal

Teal’s free tier is surprisingly useful. You get a job tracker, a resume builder, and basic career content without paying anything. The resume builder does keyword analysis and formatting suggestions that are genuinely helpful for getting past ATS filters.

Interview prep is secondary here. Teal has some interview content and tracking features, but it’s not a dedicated practice tool. Paid plans add AI-powered resume tailoring and more advanced features, but the core value is in organizing your job search.

If you want a free starting point for managing applications and building a solid resume, Teal is a reasonable choice. For interview practice specifically, you’ll want something else alongside it.

Price: Free tier available, paid plans for AI features
Best for: Budget-conscious job seekers who need a job tracker and resume builder

Rezi AI

Rezi is a resume specialist. It does one thing and does it well. The AI analyzes your resume against the job description, optimizes for ATS compatibility, suggests keyword improvements, and helps with formatting. At $29/month, it’s a focused tool at a reasonable price.

What Rezi doesn’t do: interview practice, cover letters (beyond basic generation), job discovery, negotiation coaching, or email drafting. If your resume is your only bottleneck, Rezi handles it. If you need help with the rest of the job search, you’ll need additional tools.

Price: $29/month
Best for: People whose primary need is resume optimization

Yoodli

Yoodli takes a different angle. Instead of evaluating what you say, it evaluates how you say it. The AI tracks your speaking pace, filler words (“um,” “like,” “you know”), eye contact, and overall delivery. It’s a communication coach, not an interview content coach.

This is genuinely useful for people who know their material but struggle with delivery. If you say “um” 47 times in a two-minute answer (more common than you’d think), Yoodli will show you. If you speak too fast when nervous, it’ll flag that.

The limitation is clear: Yoodli won’t help you structure a better answer to “tell me about a time you led a cross-functional project.” It doesn’t evaluate content quality, STAR method usage, or technical accuracy. Pair it with a content-focused tool for full coverage.

Price: Free tier available, paid plans for advanced features
Best for: People who need help with speaking delivery and filler words

Jobright

Jobright takes an agent-first approach to job search. Instead of browsing listings yourself, an AI agent scans openings across major job boards, matches them to your profile, and can even auto-apply on your behalf. The platform is backed by Indeed, which gives it access to a massive job data pipeline.

The job matching is where Jobright shines. It learns your preferences over time and surfaces roles you might not have found on your own. The auto-apply feature is polarizing. Some people love the volume, others worry about quality control when an AI is submitting applications for them.

Interview prep exists but it’s clearly secondary. Jobright offers basic mock interview practice and resume optimization, but neither has the depth of a dedicated tool. If your main bottleneck is finding and applying to the right jobs, Jobright is strong. If your bottleneck is actually performing well in interviews, you’ll want something else alongside it.

Price: Free tier available, premium plans ~$30-50/month
Best for: Job seekers who want an AI agent to automate job discovery and applications

Google Interview Warmup

Google’s free interview practice tool. No signup required. You open it in your browser, pick a field (data analytics, IT support, UX design, or general), and answer questions out loud. It transcribes your response and highlights talking points you covered or missed.

That’s it. No scoring, no follow-up questions, no improvement tracking over time. The question library is small and the feedback is surface-level.

For someone who has never practiced an interview question out loud before, this is a fine place to spend 20 minutes. It removes every barrier to getting started. But you’ll outgrow it fast. Think of it as a warm-up, not a training program.

Price: Free
Best for: Absolute beginners who want zero commitment

The real question: one tool or five?

Here’s the practical problem nobody talks about. You sign up for a resume tool, a separate interview tool, maybe a negotiation tool. None of them talk to each other. Your resume data doesn’t inform your interview prep. Your job applications aren’t connected to your practice sessions. You’re logging into three dashboards and paying three subscriptions.

Some people prefer that approach. Best-in-class specialists for each task. If you have the budget and don’t mind the fragmentation, there’s nothing wrong with it.

But for most job seekers, especially those between roles and watching their spending, an integrated platform that handles the full pipeline saves both money and time. Four-Leaf takes this approach. So does Career.io to a lesser degree. This isn’t a sales pitch. It’s a real consideration when you’re comparing $20/month for everything versus $99 + $29 + whatever else for the same coverage from separate tools.

More tools in this space are moving toward integrated platforms. The question is whether any single tool can match the depth of the best specialists. Right now, the honest answer is “sometimes yes, sometimes not yet.”

Quick comparison

Pricing as of April 2026. Check each tool’s website for current plans.

Tool Mock interviews Resume builder Cover letters Job search Negotiation coaching Price
Four-Leaf ✓ ✓ ✓ ✓ ✓ $20/mo
Final Round AI ✓ ✓ ✓ ✗ ✗ $150/mo
Big Interview ✓ ✗ ✗ ✗ ✗ ~$79/mo
Career.io ✓ ✓ ✓ ✗ ✗ $25-79/mo
Exponent ✓ ✗ ✗ ✗ ✗ $99/mo
Teal Limited ✓ ✗ ✓ ✗ Free tier
Rezi AI ✗ ✓ Limited ✗ ✗ $29/mo
Yoodli ✓ (delivery only) ✗ ✗ ✗ ✗ Free tier
Jobright Limited ✗ ✗ ✓ ✗ Free tier
Google Interview Warmup ✓ ✗ ✗ ✗ ✗ Free

How to choose

Three scenarios:

You want everything in one place for the lowest cost. That’s Four-Leaf at $20/month. It’s the only tool on this list with all five capabilities (interviews, resume, cover letters, job search, negotiation) in one product. Career.io is the next closest, in the $25 to $79 range depending on plan.

You only need interview practice and budget isn’t a concern. Final Round AI is the most established option for dedicated interview prep at $150/month month-to-month (or $25/month billed yearly). Exponent is strong if you’re targeting tech or PM roles specifically.

You’re a student with a tight budget. Check if your school offers Big Interview or a similar tool for free. Supplement with Google Interview Warmup and Teal’s free tier. You can get decent coverage without spending anything.

Whatever you pick, the tool matters less than the reps. Practice out loud. Practice until the real interview feels routine. Practice until your answers come naturally instead of sounding rehearsed. The best tool in the world doesn’t help if it stays in your browser bookmarks. Open something today and say your first answer out loud.

Related reading:

Package Next.js App as Nix Derivation and deploy as Service on NixOS

With this method you can package any type of Next.js App including Apps w/ SSR, API Routes, Server Actions and Proxy (Middleware).

Prerequisites

  • Obviously Git
  • Your Next.js App remote Repository w/ package.json and package-lock.json
  • Your NixOS Server

Getting started

  • Add the required output mode to your next.config.ts:
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
    output: "standalone",
};

export default nextConfig;
  • Your npmDepsHash is the unique id of your package-lock.json. We will need it for the next step, so prefetch it:
nix run --extra-experimental-features "nix-command flakes" nixpkgs#prefetch-npm-deps package-lock.json
  • Create your own flake.nix in the same directory as the package.json, make sure to edit the generic “Your Next.js App” Labels and insert your prefetched npmDepsHash from the last step:
{
  description = "Your Next.js App Description";

  # Feel free to choose the input channel yourself 
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      # Support all Nix-capable Systems
      supportedSystems = [
        "x86_64-linux"   # Standard Intel/AMD Linux
        "aarch64-linux"  # ARM Linux
        "x86_64-darwin"  # Intel Macs
        "aarch64-darwin" # Apple Silicon Macs
      ];

      forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
    in {
      packages = forAllSystems (system:
        let
          pkgs = nixpkgs.legacyPackages.${system};
        in {
          default = pkgs.buildNpmPackage {
            pname = "your-nextjs-app";
            version = "1.0.0";
            src = ./.;

            # Insert your prefetched npmDepsHash from the last step
            npmDepsHash = "sha256-...";

            postBuild = ''
              cp -r public .next/standalone/
              cp -r .next/static .next/standalone/.next/
            '';

            installPhase = ''
              cp -r .next/standalone $out
            '';
          };
        }
      );

      nixosModules.default = { config, lib, pkgs, ... }:
        let
          cfg = config.services.your-nextjs-app;

          basePackage = self.packages.${pkgs.system}.default;

          customPackage = basePackage.overrideAttrs (oldAttrs: cfg.environment);
        in {
          options.services.your-nextjs-app = {
            enable = lib.mkEnableOption "Your Next.js App";

            environment = lib.mkOption {
              type = lib.types.attrsOf lib.types.str;
              default = {};
              description = "Key-Value Pairs for Build-Time and Run-Time";
              example = {
                PORT = "3000";
                NODE_ENV = "production";
              };
            };
          };

          config = lib.mkIf cfg.enable {
            systemd.services.your-nextjs-app = {
              description = "Your Next.js App Service";
              wantedBy = [ "multi-user.target" ];
              after = [ "network.target" ];

              environment = cfg.environment;

              serviceConfig = {
                ExecStart = "${pkgs.nodejs_24}/bin/node ${customPackage}/server.js";
                Restart = "always";
              };
            };
          };
        };
    };
}

With every package update and newly added package to your Next.js App you will need to prefetch your npmDepsHash again (repeat step 2) and update your flake.nix.

  • Generate your flake.lock for reproducitbility, commit and push:
nix flake lock --extra-experimental-features "nix-command flakes" && git commit -m "Added flake.nix and flake.lock" && git push
  • Update your NixOS Server. Example minimal NixOS Build Flake, keep in mind to replace all generic values:
{
  description = "Your NixOS Server";

  # Feel free to choose the input channel yourself
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    your-nextjs-app.url = "github:YourUsername/YourRepository"; 
    # Example input with Codeberg SSH: 
    # "git+ssh://[email protected]/YourUsername/YourRepository.git"
  };

  outputs = { self, nixpkgs, your-nextjs-app, ... }:
    {
      nixosConfigurations = {
        "your-hostname-x86_64" = nixpkgs.lib.nixosSystem {
          system = "x86_64-linux";
          modules = [ configuration.nix ];
        };

        "your-hostname-aarch64" = nixpkgs.lib.nixosSystem {
          system = "aarch64-linux";
          modules = [ configuration.nix ];
        };
      };
    };
}
  • Use the provided Service of your Package. Example minimal configuration.nix in the same directory as flake.nix from step 5, keep in mind to replace all generic values:
{ config, pkgs, lib, ... }:
{
  # Demo User feel free to insert your own
  users.users."your-user" = {
      isNormalUser = true;
      initialPassword = "changeme";
      extraGroups = [ "wheel" ];
      openssh.authorizedKeys.keys = "Your .pub SSH Key";
    };

  services.your-nextjs-app = {
    enable = true;

    # Define all environment variables your App needs
    environment = {
      PORT = "3000";
      NODE_ENV = "production";
    };
  };

  networking = {
    hostname = "your-hostname";

    firewall = {
      enable = true;

      # Open the Port used by your Next.js App
      allowedTCPPorts = [ 3000 ];
    };
}
  • Rebuild your NixOS System, keep in mind to replace all generic values:
nixos-rebuild switch --flake /path/to/flakeAndConf#<your-hostname-x86_64> or <your-hostname-aarch64> --extra-experimental-features "nix-command flakes"
  • Now the your Next.js App should be available as a Service on your NixOS Server. Enjoy <3