跪拜 Guibai
← Back to the summary

DeepSeek V4 Pro Tested Across Four Frontend Scenarios: Agent Benchmarks Jump 8.6x, but Real-World Speed Tells a Different Story

Last night, DeepSeek quietly released V4 Pro.

No press conference, no official tweet, not even a teaser poster. I was scrolling through a tech group when someone dropped a message: "DeepSeek V4 Pro is up, DeepSWE 7.3 → 62.7."

62.7? The same DeepSWE that was only 7.3 before? An 8.6x surge?

Honestly, I've seen too many benchmark scores. Terminal Bench 87.9, DeepSWE 62.7 — the numbers look pretty. But as a frontend engineer, I don't care about a few tenths of a point on a benchmark. I care about:

Can it write React for me? Can it find bugs? Can it scaffold a project from scratch?

Let's test it. Today, using four real-world frontend scenarios — from component generation to bug fixing to agent engineering tasks — I'll run it against the real API and see what V4 Pro is really made of.


1. What Exactly Did V4 Pro Upgrade?

1.1 Core Specs

First, let's look at V4 Pro's fundamentals:

Spec V4 Pro vs. Previous Gen
Context Window 1M tokens Industry-leading
Max Output 384K tokens Significantly improved long-output capability
Thinking Mode Three tiers (Low/Medium/High) Selectable reasoning depth on demand
Model Codename deepseek-v4-pro

A 1M context means you can throw the entire source code of a frontend project at it for global analysis. 384K output means it can generate a complete project scaffold in one go without cutting off halfway.

The three-tier thinking mode is an interesting design — use Low for simple questions to get fast responses, and High for complex tasks requiring deep reasoning. As you'll see in the tests later, this design directly impacts response speed and quality.

1.2 Agent Evaluation Data Comparison

The most explosive data for V4 Pro this time is in Agent evaluations:

Tool Terminal Bench 2.1 DeepSWE
DeepSeek V4 Pro 87.9 62.7
Claude Fable 5 88.0
Claude Opus 4.8 <87.9 <62.7
Meta Muse Code 82.9 59.3
OpenAI Codex (GPT-5.6) 81.8

On Terminal Bench, it basically ties with Claude Fable 5, 87.9 vs 88.0 — a 0.1 difference is negligible. On DeepSWE, 62.7 puts it a full step ahead.

But even more shocking is DeepSWE's own improvement: preview version 7.3 → official version 62.7, roughly an 8.6x increase. A leap of this magnitude is rare in model iteration history.

There's also a SuperCLUE-Terminal evaluation worth referencing for Chinese-language scenarios: Kimi K3 is first (60.61), DeepSeek V4 Pro second (51.52), GLM-5.2 third (48.48). DeepSeek didn't take first in Chinese scenarios, but the gap isn't large.

Now look at the pricing:

Tool Input Price (per million tokens) Output Price (per million tokens)
DeepSeek V4 Pro ¥3 ¥6
Claude Fable 5 ~¥108 ($15) ~¥540 ($75)
Meta Muse Code ~¥9 ($1.25) ~¥30 ($4.25)

DeepSeek's input price is 1/36th of Claude's, and its output price is 1/90th. Even compared to the cheapest, Muse Code, it's significantly cheaper.

Estimating by exchange rate, a single complex Agent task for Claude Fable 5 (assuming 50K input + 100K output consumed) costs roughly ¥10.8 + ¥54 = ¥64.8. The same task on DeepSeek costs only ¥0.15 + ¥0.6 = ¥0.75. 1/86th the cost of Claude.

A price butcher, true to its name.

1.3 The Harness Behind the Scores: It's Not the Bare Model

These benchmark scores weren't achieved by the bare model.

The official evaluation for the V4 Pro official version used the DeepSeek Harness minimal mode + max thinking tier. What is Harness? Simply put, it's the Agent framework (v0.1 developer preview, MIT open-source) released on 8/13 alongside V4 Pro. It's responsible for calling tools, reading/writing files, managing context, and handling errors — the engineering layer that lets a model carry a task through from start to finish.

DeepSeek Harness's design philosophy is "everything is a plugin" — models, tools, skills, sessions, sandboxes, storage, loops, scheduling, UI, all plugin-based, freely replaceable and recombinable.

What does this mean? The official scores represent the combined capability of "model + framework," not the bare model capability. When you call the API directly without the Harness, performance might differ from the scores — because you lack that layer of tool-calling and context management support.

My four rounds of testing below are bare API calls, without Harness. You can understand my test results as the real-world performance without the framework.

1.4 Why Frontend Developers Should Care

The competitive landscape for AI coding tools in 2026 is clear: Claude Code takes the high-end route, Codex relies on the GPT ecosystem, Muse Code champions open-source and free, and DeepSeek is the cost-effectiveness killer. The underlying model capabilities of these tools directly determine your daily coding efficiency.

The specificity of frontend scenarios lies in: large code volume (JSX + CSS + type definitions), strong cross-file dependencies (Component → Hook → Store → API), and high demands on context understanding (you need to understand the entire component tree just to change a prop's type).

V4 Pro's 1M context + 384K output + deep thinking mode happen to hit the pain points of frontend development.

So, what's the actual effect?


2. Test 1: React Component Generation

2.1 Test Design

The first scenario is the most basic and common — having AI generate a complete React component. I gave it a classic requirement: a TodoList, but with state management and filtering functionality.

The prompt was as follows:

Write a complete TodoList component using React + TypeScript, requirements:

  1. Use useReducer for state management
  2. Support add, delete, toggle completion status
  3. Support filtering (All/Completed/Active)
  4. Include complete TypeScript type definitions
  5. Include complete CSS styles (using CSS variables)
  6. Responsive design
  7. Do not use any third-party libraries

2.2 API Call Code

Calling the DeepSeek V4 Pro API is simple, compatible with the OpenAI format:

import requests

API_KEY = "your-api-key"  // Replace with your own key
URL = "https://api.deepseek.com/v1/chat/completions"
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json"
}

payload = {
    "model": "deepseek-v4-pro",
    "messages": [
        {"role": "user", "content": "Write a complete TodoList component using React + TypeScript..."}
    ],
    "max_tokens": 16384,
    "stream": False
}

response = requests.post(URL, headers=HEADERS, json=payload)
result = response.json()

content = result["choices"][0]["message"]["content"]
reasoning = result["choices"][0]["message"].get("reasoning_content", "")

print(f"Output content:\n{content}")
print(f"\nThinking process:\n{reasoning}")

🆕 Note the reasoning_content field — this is the output of V4 Pro's thinking mode. The model "thinks" first before answering. This field is very useful for debugging and understanding model behavior.

2.3 Complete Output

The model took 130.7 seconds, consuming a total of 9003 tokens (prompt=160, completion=8843, of which reasoning=5976, content=2867).

Nearly 6000 tokens were spent on thinking, with less than 3000 tokens of actual output. In other words, the model spent 2/3 of its compute on "thinking" and 1/3 on "writing."

Below is V4 Pro's complete output (core parts excerpted):

// types.ts complete type definitions
interface Todo {
  id: string;
  text: string;
  completed: boolean;
  createdAt: number;
}

type FilterType = 'all' | 'active' | 'completed';

type Action =
  | { type: 'ADD_TODO'; payload: string }
  | { type: 'TOGGLE_TODO'; payload: string }
  | { type: 'DELETE_TODO'; payload: string }
  | { type: 'SET_FILTER'; payload: FilterType };

The type definitions are very standard. Todo has complete fields, and Action uses a discriminated union — this is the best practice for writing React reducers.

// reducer.ts
function todoReducer(state: Todo[], action: Action): Todo[] {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: Date.now().toString(),
          text: action.payload,
          completed: false,
          createdAt: Date.now(),
        },
      ];
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    case 'DELETE_TODO':
      return state.filter(todo => todo.id !== action.payload);
    case 'SET_FILTER':
      return state; // filter is managed in a separate state
    default:
      return state;
  }
}

There's a minor flaw here — the SET_FILTER case directly returns state because the filter state needs to be managed separately. The model realized this issue during its thinking process, but in implementation, it split the filter into a separate useState. It's reasonable, but leaving a no-op case in the reducer's switch is slightly inelegant.

// TodoList.tsx
function TodoList() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const [filter, setFilter] = useState<FilterType>('all');
  const inputRef = useRef<HTMLInputElement>(null);

  const handleAdd = () => {
    const text = inputRef.current?.value.trim();
    if (!text) return;
    dispatch({ type: 'ADD_TODO', payload: text });
    if (inputRef.current) inputRef.current.value = '';
  };

  const filteredTodos = todos.filter(todo => {
    if (filter === 'active') return !todo.completed;
    if (filter === 'completed') return todo.completed;
    return true;
  });

  return (
    <div className="todo-container">
      <header className="todo-header">
        <h1>Todo List</h1>
        <div className="todo-input-group">
          <input ref={inputRef} type="text" placeholder="Enter new task..." />
          <button onClick={handleAdd}>Add</button>
        </div>
      </header>
      <div className="todo-filters">
        {(['all', 'active', 'completed'] as FilterType[]).map(f => (
          <button
            key={f}
            className={filter === f ? 'active' : ''}
            onClick={() => setFilter(f)}
          >
            {{ all: 'All', active: 'Active', completed: 'Completed' }[f]}
          </button>
        ))}
      </div>
      <ul className="todo-list">
        {filteredTodos.length === 0 ? (
          <li className="todo-empty">No tasks</li>
        ) : (
          filteredTodos.map(todo => (
            <li key={todo.id} className={todo.completed ? 'completed' : ''}>
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
              />
              <span className="todo-text">{todo.text}</span>
              <button onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}>
                Delete
              </button>
            </li>
          ))
        )}
      </ul>
    </div>
  );
}

The component implementation is very complete. useReducer + useState + useRef are used together, the filtering logic is clear, and the empty state is handled.

CSS section (excerpt):

:root {
  --primary-color: #4f46e5;
  --bg-color: #f9fafb;
  --text-color: #111827;
  --border-color: #e5e7eb;
  --completed-color: #9ca3af;
}

.todo-container {
  max-width: 600px;
  margin: 2rem auto;
  padding: 1.5rem;
  background: var(--bg-color);
  border-radius: 12px;
}

@media (max-width: 640px) {
  .todo-container {
    margin: 0;
    border-radius: 0;
    min-height: 100vh;
  }
}

CSS variables, responsiveness, transition animations — all present. The model even considered mobile adaptation.

2.4 Quality Assessment

First test, good impression. A single prompt produced a basically usable component, requiring very little manual modification. The 130-second wait was a bit long, but the thinking mode was set to High — trading speed for quality is understandable.


3. Test 2: Advanced TypeScript Type Inference

3.1 Test Design

The second scenario examines the depth of the type system — having V4 Pro implement two classic advanced type utilities: DeepPartial and GetValueType. These two types aren't common in frontend projects, but they can test the model's depth of understanding of the TS type system.

Prompt:

Implement the following TypeScript advanced type utilities:

  1. DeepPartial — Recursively make all properties optional, including nested objects and arrays
  2. GetValueType — Extract the union type of all object values

Requirements: Handle edge cases (functions, arrays, null/undefined), provide usage examples for each type

3.2 Output

Took 36.9 seconds, consumed 2423 tokens (prompt=140, completion=2283, of which reasoning=1854, content=429).

Much faster this time — 36 seconds vs. the previous 130 seconds. The reason is the problem was more focused, requiring less thinking (1854 vs. 5976).

// 1. DeepPartial<T> — Recursive Optional
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object
    ? T[P] extends Function
      ? T[P]          // Functions kept as-is, no recursion
      : T[P] extends Array<infer U>
        ? Array<DeepPartial<U>>  // Arrays: recursively process elements
        : DeepPartial<T[P]>      // Plain objects: recurse
    : T[P];
};

// Usage example
interface User {
  id: number;
  profile: {
    name: string;
    age: number;
    address: {
      city: string;
      zip: string;
    };
  };
  tags: string[];
  onClick: () => void;
}

type PartialUser = DeepPartial<User>;
// Equivalent to:
// {
//   id?: number;
//   profile?: {
//     name?: string;
//     age?: number;
//     address?: {
//       city?: string;
//       zip?: string;
//     };
//   };
//   tags?: string[];
//   onClick?: () => void;
// }

Let's break down this DeepPartial line by line:

  1. [P in keyof T]? — Mapped type, iterates all keys and adds ? to make optional
  2. T[P] extends object — Checks if the property value is an object type
  3. T[P] extends Function — Function types kept as-is, no recursion (edge case handling)
  4. T[P] extends Array<infer U> — Array types: recursively process element types
  5. DeepPartial<T[P]> — Plain objects: recurse

Both functions and arrays have edge case handling, which many people easily miss when handwriting DeepPartial. V4 Pro explicitly considered these edge cases during its thinking process — those 1854 tokens of reasoning weren't wasted.

// 2. GetValueType<T> — Extract union type of values
type GetValueType<T> = T[keyof T];

// Usage example
interface Config {
  port: number;
  host: string;
  debug: boolean;
}

type ConfigValues = GetValueType<Config>;
// Equivalent to: number | string | boolean

// More complex example
interface AppState {
  user: { name: string };
  settings: { theme: string };
  count: number;
}

type AppStateValues = GetValueType<AppState>;
// Equivalent to: { name: string } | { theme: string } | number

The core of GetValueType is just T[keyof T] — iterating all keys to get the union type of their values. Simple but practical, often used in Redux/state management scenarios.

3.3 Quality Assessment

This round performed very well. The type system is the deep end of TS, and V4 Pro's understanding of conditional types, infer, and mapped types is solid. The function edge case handling is a bonus point — many models forget that functions are also a subtype of object, causing () => void to be recursively expanded.


4. Test 3: Bug Fixing Capability

4.1 Test Design

The third scenario is bug fixing, also the highest-frequency scenario for daily AI coding tool usage.

I gave V4 Pro a piece of buggy React component code and asked it to find all issues and fix them.

Prompt:

The following React component code has multiple bugs. Please find all bugs and provide the fixed complete code:

function UserList({ userId }) {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    fetch(`/api/users?page=${userId}`)
      .then(res => res.json())
      .then(data => setUsers(data.users));
  }, []);
  
  return (
    <ul>
      {users.map((user, index) => (
        <li key={index}>{user.name}</li>
      ))}
    </ul>
  );
}

This code has three classic bugs, the kind you've probably seen in code reviews.

4.2 Output

Took 182.2 seconds, consumed 11981 tokens (prompt=286, completion=11695, of which reasoning=11023, content=672).

182 seconds! Over 3 minutes. The 11023 tokens of thinking were the largest among all tests. The model did very detailed analysis during the thinking phase.

V4 Pro identified 3 bugs:

Bug 1: useEffect dependency array missing userId

// Original code
useEffect(() => {
  fetch(`/api/users?page=${userId}`)
    .then(res => res.json())
    .then(data => setUsers(data.users));
}, []);  // ← Empty dependency array, won't re-fetch when userId changes

This is the most classic React Hooks bug — userId is used inside the effect but not added to the dependency array. When userId changes after mount, data won't reload.

Bug 2: Missing async request cleanup / race condition handling

// Original code doesn't handle cleanup on component unmount
// If userId changes rapidly, old requests might overwrite new request results

V4 Pro introduced AbortController to handle race conditions — this bonus point was unexpected. Many models only add the dependency array, not considering race conditions.

Bug 3: Using unreliable index as key

// Original code
{users.map((user, index) => (
  <li key={index}>{user.name}</li>  // ← index as key is unstable
))}

When lists are deleted/sorted, the index changes, causing React's diff algorithm to incorrectly reuse DOM elements. A stable unique identifier, like user.id, should be used.

Fixed complete code:

function UserList({ userId }: { userId: string }) {
  const [users, setUsers] = useState<User[]>([]);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController(); // AbortController handles race conditions

    const fetchData = async () => {
      try {
        setError(null);
        const response = await fetch(`/api/users?page=${userId}`, {
          signal: controller.signal, // Pass signal
        });

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        const data = await response.json();
        setUsers(data.users);
      } catch (err) {
        if (err instanceof DOMException && err.name === 'AbortError') {
          return; // Request was cancelled, handle silently
        }
        setError(err instanceof Error ? err.message : 'Unknown error');
      }
    };

    fetchData();

    return () => controller.abort(); // Cleanup function cancels unfinished requests
  }, [userId]); // Dependency array completed

  if (error) {
    return <div className="error">Load failed: {error}</div>;
  }

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li> // Use user.id instead of index
      ))}
    </ul>
  );
}

The fix quality is very high. All three bugs are fixed, and additionally:

4.3 Quality Assessment

Honestly, I was surprised. Introducing AbortController shows V4 Pro wasn't just fixing the surface, but thinking about "what problems will this code encounter at runtime." This is the mindset of a senior engineer.

182 seconds is indeed very long. But considering it thought for 11,000 tokens (equivalent to writing a 5,000-word technical analysis in its head), this time was well spent. For complex bug-fixing scenarios, being a bit slow is fine, as long as it's thorough.


5. Test 4: Agent Multi-Step Engineering Task

5.1 Test Design

The final scenario is having V4 Pro act as an Agent to complete a multi-step engineering task: creating a React + Vite + TypeScript project from scratch, including components, routing, and API calls.

Prompt:

As a frontend engineering Agent, complete step-by-step:

  1. Create a React + TypeScript project with Vite
  2. Install react-router-dom
  3. Create src/types.ts (type definitions)
  4. Create src/api.ts (fetch wrapper with error handling)
  5. Create src/pages/Home.tsx and src/pages/Detail.tsx
  6. Create src/App.tsx (HashRouter routing configuration)
  7. Provide the directory structure tree

Provide complete code for each step.

API Data: Took 154.4s | Total tokens 13804 (thinking 11147, output 2452)

5.2 Output

V4 Pro output a complete setup guide step-by-step:

Step 1: Project Initialization

npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm install react-router-dom
npm install -D @types/react-router-dom

Step 2: Type Definitions

// src/types.ts
export interface Post {
  id: number;
  userId: number;
  title: string;
  body: string;
}

Step 3: API Wrapper

// src/api.ts
import { Post } from './types';

const BASE_URL = 'https://jsonplaceholder.typicode.com';

export async function fetchPosts(signal?: AbortSignal): Promise<Post[]> {
  const res = await fetch(`${BASE_URL}/posts`, { signal });
  if (!res.ok) throw new Error(`HTTP ${res.status}`); // Status code check
  return res.json();
}

export async function fetchPostById(id: number, signal?: AbortSignal): Promise<Post> {
  const res = await fetch(`${BASE_URL}/posts/${id}`, { signal });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

Two points worth noting in the API wrapper: res.ok check — just like in the bug fix test, V4 Pro consistently has good habits for HTTP error handling; signal?: AbortSignal optional parameter — allows callers to pass AbortController.signal to cancel requests. This parameter design is the foundation for race condition handling in subsequent page components.

Step 4: Routing Configuration

// src/App.tsx
import { HashRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import Detail from './pages/Detail';

function App() {
  return (
    <HashRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/post/:id" element={<Detail />} />
      </Routes>
    </HashRouter>
  );
}

export default App;

Using HashRouter instead of BrowserRouter — the model noticed the "Hash mode" requirement in the prompt. This kind of detail adherence is important.

Step 5: Home Page

// src/pages/Home.tsx
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { fetchPosts } from '../api';
import { Post } from '../types';

function Home() {
  const [posts, setPosts] = useState<Post[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();

    const load = async () => {
      try {
        setLoading(true);
        const data = await fetchPosts(controller.signal);
        setPosts(data);
      } catch (err) {
        if (err instanceof DOMException && err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    };

    load();
    return () => controller.abort();
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      <h1>Article List</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <Link to={`/post/${post.id}`}>{post.title}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default Home;

AbortController appears again — V4 Pro has a strong awareness of async request cleanup. Or perhaps the underlying model has adopted this thinking pattern as default behavior.

Step 6: Detail Page

// src/pages/Detail.tsx
import { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
import { fetchPostById } from '../api';
import { Post } from '../types';

function Detail() {
  const { id } = useParams<{ id: string }>();
  const [post, setPost] = useState<Post | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const controller = new AbortController();

    const load = async () => {
      try {
        setLoading(true);
        if (id) {
          const data = await fetchPostById(Number(id), controller.signal);
          setPost(data);
        }
      } catch (err) {
        if (err instanceof DOMException && err.name !== 'AbortError') {
          console.error(err);
        }
      } finally {
        setLoading(false);
      }
    };

    load();
    return () => controller.abort();
  }, [id]); // id as dependency

  if (loading) return <div>Loading...</div>;
  if (!post) return <div>Article not found</div>;

  return (
    <div>
      <Link to="/">← Back to list</Link>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </div>
  );
}

export default Detail;

The generic parameter <{ id: string }> for useParams is written, id is added to the dependency array, and the !post empty state is handled.

5.3 Quality Assessment

It's clear the entire Agent task was completed with high quality. 7 steps, 10+ files, each file's code complete and runnable.

V4 Pro maintained a consistent code style across different files. The usage pattern of AbortController is completely consistent in request.ts and the page components. This cross-file consistency indicates the model has an internal code standard awareness, not generating randomly.


6. Horizontal Comparison

6.1 Capability Comparison

Let's place V4 Pro in the current competitive landscape of AI coding tools:

Tool Terminal Bench 2.1 DeepSWE Chinese Eval SuperCLUE
DeepSeek V4 Pro 87.9 62.7 51.52 (2nd)
Claude Fable 5 88.0
Meta Muse Code 82.9 59.3
OpenAI Codex (GPT-5.6) 81.8
Kimi K3 60.61 (1st)
GLM-5.2 48.48 (3rd)

On Terminal Bench, DeepSeek and Claude are basically tied. On DeepSWE, DeepSeek leads. In Chinese scenarios, Kimi K3 performs better, but Kimi's Agent capability (DeepSWE) has no public data.

Note: DeepSeek V4 Pro's Terminal Bench/DeepSWE data is based on scores run with the official Harness framework. Other models' environments vary, comparison is for trend reference only.

6.2 Price Comparison

Tool Input (per million tokens) Output (per million tokens) Relative Cost
DeepSeek V4 Pro ¥3 ¥6 1x (baseline)
Meta Muse Code ~¥9 ~¥30 3-5x
Claude Fable 5 ~¥108 ~¥540 36-90x
OpenAI Codex Subscription-based

DeepSeek's price advantage is crushing. The cost of the same task volume on Claude could cover your DeepSeek usage for a month.

6.3 Frontend Developer Selection Advice

Based on my experience from these four rounds of testing, here's a practical selection guide for frontend developers:

Daily code completion / quick generation → Muse Code or DeepSeek thinking mode Low

Complex component generation / Bug fixing → DeepSeek V4 Pro thinking mode High

Large project refactoring / Global understanding → Claude Fable 5 (if budget allows)

Chinese scenarios / Domestic projects → DeepSeek V4 Pro or Kimi K3

If you're an individual developer or a small team, DeepSeek V4 Pro is currently the most cost-effective choice, bar none. If you're at a large company with a budget, use a combination of Claude Fable 5 + DeepSeek V4 Pro — complex tasks to Claude, daily tasks to DeepSeek.


7. Thoughts and Limitations

7.1 Thinking Mode is Slow, But Not Needed for All Scenarios

Across four rounds of testing, it's clear that the larger the thinking volume, the longer the time, but the higher the result quality.

Bug fixing took 182 seconds, but it found 3 bugs and introduced AbortController — something a fast model can't give you.

V4 Pro's three-tier thinking mode is designed to solve this. Use Low for daily completions for fast responses, High for complex analysis requiring deep reasoning. However, currently the switching of thinking modes at the API level isn't transparent enough, and the documentation is relatively sparse. Looking forward to future improvements.

7.2 Not Suitable for Real-Time Completion

Response times of 130-182 seconds mean V4 Pro is not suitable for real-time code completion in the IDE. Typing a character in VS Code and waiting 2 minutes for a suggestion is definitely a bad experience.

For completion scenarios, it's better to use simpler tools or plugins, which are free and work well. Use V4 Pro for complex Agent tasks.

7.3 The Price Advantage is Real

What does the ¥3/¥6 price mean?

The four rounds of testing consumed roughly 33,000 tokens in total. Calculated at Claude Fable 5's prices, that's about ¥5.4. Calculated at DeepSeek's prices, about ¥0.13.

If you run 1,000 similar tasks in a month, Claude costs ¥5,400, DeepSeek only ¥130. The money saved could buy a MacBook.

7.4 What Other Reviewers Found

I also looked at other people's reviews for cross-validation.

Programmer Yupi (程序员鱼皮) ran 7 projects (animation/3D/game/full-stack) using Codex + self-built Harness, concluding that "frontend effects are poor, backend logic is solid" — frontend UI rendering effects are noticeably worse than Kimi K3 and Claude Opus 5, but backend concurrency control and logical rigor are fine.

Xi Xiaoyao Tech (夕小瑶科技说) ran 41 million tokens, concluding it's "a good model but not a good colleague" — V4 Pro works silently in Codex without speaking (thinking accounts for 84.5%), likes to copy homework from old sessions of the same model family, and frontend and writing capabilities are below expectations. But the foundation is solid: 100 concurrency with 0 failures, cache hit rate 96%.

These findings align with my test results: V4 Pro's code logic and bug insight are strong (backend thinking), but frontend UI rendering is not its strength. My four rounds of testing leaned towards the code logic side, so performance was good. If you need it for complex UI design or visual effects, lower your expectations.

There's also a consensus: V4 Pro's thinking mode is slow and not suitable for real-time completion. Yupi mentioned p50 latency of 4.9s, p99 latency of 12.3s. Although the magnitude differs from my 130-182s level (he was running Codex scenarios, I was doing single API calls), the conclusion of "slow" is consistent.

7.5 Pride in Domestic Models

V4 Pro ties with Anthropic's flagship model on Terminal Bench and surpasses all competitors on DeepSWE. This isn't "domestic models have caught up," it's "domestic models are leading in certain dimensions."

As a development engineer, I only care about whether it's good to use and whether it's cheap. V4 Pro is good and cheap, it's that simple.


Interview Questions to Wrap Up

Question 1: useEffect Dependency Array and Race Condition Handling

Question: What problems will the following component encounter when userId switches rapidly? How to fix it?

function UserList({ userId }) {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(setData);
  }, [userId]);
  return <div>{data?.name}</div>;
}

Answer:

Problem: When userId switches rapidly, an old request might return later than a new request, causing setData to be overwritten by stale data (race condition).

Fix — Use AbortController:

useEffect(() => {
  const controller = new AbortController();
  fetch(`/api/users/${userId}`, { signal: controller.signal })
    .then(res => res.json())
    .then(setData)
    .catch(err => {
      if (err instanceof DOMException && err.name === 'AbortError') return;
      console.error(err);
    });
  return () => controller.abort();
}, [userId]);

Analysis: useEffect's cleanup function executes when dependencies change or the component unmounts. controller.abort() cancels all unfinished requests that were passed that signal. Cancelled fetches throw an AbortError, which needs to be silently handled in the catch block. This is the core idea V4 Pro used in the bug fix test.


Question 2: useReducer vs useState Selection

Question: In what scenarios should you use useReducer instead of useState? Provide examples.

Answer:

Scenarios for using useReducer:

  1. Complex state transition logic involving multiple linked sub-states
  2. The next state depends on multiple fields of the previous state
  3. State update logic needs to be reused
  4. Want centralized management and tracking of state changes

Example — A TodoList component has three states: todos, filter, loading. Using useState would require three independent setters, with logic scattered. Using useReducer defines all operations as Actions, with the reducer centrally handling state transitions:

// ✅ useReducer: Centralized state transition logic
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'ADD_TODO', payload: text });

// ❌ useState: Logic scattered across multiple event handler functions
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');
// Each operation requires manually managing multiple setters

Analysis: V4 Pro automatically chose the useReducer + useState combination in the component generation test — reducer manages the todos array, useState manages the filter. This is a reasonable architectural choice: core data centrally managed with reducer, independent UI state lightly handled with useState.


Question 3: TypeScript DeepPartial Implementation

Question: Handwrite a DeepPartial<T> type that recursively makes all properties optional, handling edge cases for functions and arrays.

Answer:

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object
    ? T[P] extends Function
      ? T[P]
      : T[P] extends Array<infer U>
        ? Array<DeepPartial<U>>
        : DeepPartial<T[P]>
    : T[P];
};

Analysis:

The key lies in the three-layer conditional type check:

  1. T[P] extends object — First check if it's an object type
  2. T[P] extends Function — Keep functions as-is, no recursion (otherwise () => void would be expanded to { (): void })
  3. T[P] extends Array<infer U> — Arrays: recursively process element type U, not the Array itself

V4 Pro handled these three edge cases completely correctly in the type inference test. Many people miss the function edge case when handwriting DeepPartial, because in JavaScript typeof fn === 'function' but Function extends object is also true.


Question 4: AbortController Usage

Question: Explain the working principle of AbortController and provide a complete pattern for using it in React.

Answer:

AbortController is a request cancellation mechanism provided by the Web API:

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const controller = new AbortController();

    const fetchData = async () => {
      try {
        setLoading(true);
        const res = await fetch(url, { signal: controller.signal });
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const json = await res.json();
        setData(json);
      } catch (err) {
        if (err instanceof DOMException && err.name === 'AbortError') {
          return; // Cancelled request, handle silently
        }
        setError(err as Error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
    return () => controller.abort(); // ✅ Cleanup function
  }, [url]);

  return { data, error, loading };
}

Analysis:

Working principle in three steps:

  1. Create an AbortController instance, get controller.signal
  2. Pass signal to fetch(url, { signal })
  3. Call controller.abort() to cancel all requests using that signal

Usage pattern in React: Create the controller inside useEffect, call abort() in the cleanup function. This way, when dependencies change or the component unmounts, unfinished requests are automatically cancelled.

V4 Pro used this pattern in both the bug fix and Agent tasks — indicating its training data contains a large amount of modern React best practice code.


Question 5: Selection Considerations for AI Coding Tools

Question: As a frontend team lead, you need to choose an AI coding tool for a 10-person team. List 3 key consideration dimensions and explain the pros and cons of DeepSeek V4 Pro and Claude Fable 5 on each dimension.

Answer:

Dimension 1: Task Matching

Dimension 2: Cost Efficiency

Dimension 3: Data Security and Compliance

Analysis: AI coding tool selection isn't just about comparing benchmark scores; it requires comprehensive consideration of team capability, budget constraints, and compliance requirements. V4 Pro has clear advantages in cost and compliance, Claude leads in ecosystem maturity. In practice, many teams adopt a "primary + backup" dual-model strategy.