FORM NOT VOID, MIND NO CORE

Chapter 4 Negative Space Design: First Define What Not to Do, Then Let It Write Code

2026.08.10

In art and design, there is an extremely important concept called "negative space." It refers to the empty areas surrounding and between the main subjects. A mediocre painter only sees the apple they are about to paint. A master, however, sees both the apple itself and the shape of the surrounding sky that is "cut out" by the apple's outline. The master understands that by carefully sculpting the "emptiness," the subject can be made more prominent, powerful, and sharply defined.

This profound idea transfers perfectly to the field of AI programming and constitutes a highest form of the art of constraint: Negative Space Design.

Traditional AI programming, which we can call "positive space design," involves racking our brains, using increasingly detailed and precise language, to tell AI "what to do." "Please write a React component using useState and useEffect, fetch data from an API, and render a list..." We try to depict that one correct "apple" within AI's infinite space of possibility.

Negative space design offers another approach. Instead of depicting only the apple, we also sculpt the "emptiness" around it. Clear "do not" instructions can eliminate known bad paths and narrow the search space, but several viable answers usually remain. A final choice still requires positive specifications, trade-off criteria, and verification.

In this chapter, we will learn how to evolve from a "prompt engineer" into a "constraint architect." The method may seem counterintuitive: tell AI what it must not do to reduce clearly unacceptable output, then use acceptance criteria to choose among what remains.

4.1 Prohibition Instructions Are More Valuable Than Implementation Instructions

When collaborating with a large language model, a well-designed "prohibition instruction" is often worth more than ten "implementation instructions." This seemingly paradoxical conclusion stems from our deep understanding of AI's nature.

Why Is "Prohibition" Sometimes More Effective? -- From "Infinite Possibilities" to a Limited Set of Candidates

The core of a large language model is a probabilistic possibility engine, not a logical certainty engine. When you give it an "implementation instruction" like "write a function to process user-uploaded images," you are essentially throwing it into a vast and boundless "ocean of possibilities."

  • It could use the Pillow library, or it could use OpenCV.
  • It could store the image in memory first, or it could save it to a temporary file first.
  • It could support PNG and JPG, or it might forget to support GIF.
  • It could return the path of the processed image, or it could return the binary data of the image.
  • ...

There are thousands of "seemingly viable" paths in this ocean. AI will choose a high-probability path based on its training data. But there is a good chance that this path is incompatible with the existing architecture, conventions, and hidden constraints in your project. So you fall into an endless "correction loop": "No, I want you to use Pillow," "No, save it to a temporary file first to prevent large files from bursting memory," "You forgot to handle WEBP format"...

Now, let us switch to the "negative space" mode of thinking. We no longer tell it how to swim. Instead, we start building dams in that ocean.

"I want you to process user-uploaded images. However:

  • Prohibit the use of OpenCV or any image processing library other than Pillow.
  • Prohibit reading the entire file into memory at once. Must use streaming or chunked writing to a temporary file.
  • Prohibit hard-coding the list of supported file formats inside the function. Must read from a global configuration file config.py.
  • Prohibit the function from returning binary data. Must return the absolute path of the processed file."

Do you see it? We did not teach AI every step. Four prohibition instructions instead exclude a set of known unacceptable paths and steer the candidate space toward the current architecture. Constraints cannot create a uniquely and absolutely correct solution by themselves: omitted requirements, conflicting rules, and false architectural premises can leave every remaining candidate unfit. Generated output must therefore pass tests, review, and operational feedback, and the constraints themselves remain revisable.

The four core values of "prohibition instructions":

  1. Greatly reduce AI's "decision fatigue" AI does not really "think"; it finds the most similar pattern in a vast vector space. Too many choices only increase the probability of it matching the wrong pattern. "Prohibition instructions" greatly purify its "thinking environment" by eliminating wrong options, making it easier for it to match the correct code pattern.

  2. Externalize your "tacit knowledge" Why do you understand your project better than AI does? Because you have a large amount of "tacit knowledge" and "engineering intuition" in your head that is difficult to describe in a few sentences. For example, "Our project must absolutely not introduce any library with C++ bindings, because that would make deployment a nightmare." This kind of knowledge is hard to convey through an "implementation instruction." But it is extremely simple with a "prohibition instruction": "Strictly forbidden to introduce any Python library that requires compiling C++ extensions." This single prohibition turns your hard-earned, blood-and-tears experience into a rule that AI can understand and strictly follow.

  3. Greatly reduce your "review cost" Reviewing whether a piece of code "implements" your intent is a very mentally taxing task. You need to understand all its logic and evaluate its efficiency and elegance.

Reviewing whether a piece of code "violates" your prohibitions, however, is much simpler. It becomes a mechanical, checklist-style inspection:

  • Did it use OpenCV? -- No.
  • Did it read the file into memory? -- No.
  • Did it hard-code file formats? -- No.
  • Did it return binary data? -- No.

Your brain is freed from the heavy burden of an "open-ended Q&A" and turned into a relaxed "multiple-choice judgment." This allows you to focus your energy on higher-level business logic review.

  1. It is the ultimate weapon against the "entropy spiral" We discussed in Chapter 2 that AI is naturally inclined to "patch." "Prohibition instructions" are the firewall against this behavior.
  • Weak instruction (implementation): "Please add caching to this function." (AI might directly add a global dictionary inside the function as a cache, causing memory leaks and thread safety issues.)
  • Strong instruction (prohibition): "Please add caching to this function. Prohibit implementing any caching logic inside the function. Must use Python's functools.lru_cache decorator."

By prohibiting "manual implementation," you force AI to use a more standard, more robust, and more engineering-best-practice-compliant solution.

From now on, please change your thinking. Before asking AI a question, do not rush to think "what should I make it do." Instead, spend a minute asking yourself: "In order for this feature to be implemented the right way, what must I prohibit it from doing?"

This question is the watershed between being an AI user and being an AI driver.

4.2 Define Capability Boundaries: Which Are Core Processes and Which Are Inviolable Red Lines

Having mastered the power of "prohibition instructions," the next question is: what should we "prohibit"?

Setting prohibitions randomly will not bring good results. An efficient constraint architect knows how to precisely identify the system's "lifelines" and build multilayered defenses around them. This process is called "defining capability boundaries."

Imagine you are designing a highly confidential research laboratory. You would not give the scientists a list of "things they can do," because scientific research is exploratory. Instead, you would design an extremely strict "environment and protocol."

  • Core area (free exploration allowed): Inside the biosafety cabinet, scientists can freely perform experiments.
  • Buffer zone (strict protocols): From the lab to the outside world, multiple layers of disinfection and inspection procedures are required.
  • Absolute red line (strictly forbidden): The lab's ventilation system, power system, and alarm system are absolutely off-limits for any scientist to modify.

Our software systems should be used to design the collaboration boundaries with AI in the same way.

Step One: Identify Your "Absolute Red Lines"

"Absolute red lines" are the core principles that, if touched or misunderstood by AI, would cause the entire project architecture to collapse, security to be breached, or the project to descend into maintenance hell. These red lines form the core content of the "Absolute Prohibitions" section of your AGENTS.md or ARCHITECTURE.md.

How do you find them? You can think from the following dimensions:

  1. The "backbone" of the architecture

These ensure that your project's "form and spirit" do not dissipate.

  • Layered principle: "Strictly forbid any module in the UI layer from directly importing a module from the data access layer."
  • Dependency direction: "Strictly forbid the domain model layer from depending on any external frameworks or libraries. It must be pure, dependency-free business logic."
  • State management: "Strictly forbid passing data from a parent component to a grandchild component through any means other than props (must use a global state manager or Context)."
  1. The "lifeline" of security

These are the last line of defense protecting your application and users from attacks.

  • Input validation: "Strictly forbid trusting any input from the client. Every API entry point must strictly validate the request body."
  • SQL injection: "Strictly forbid concatenating any variable directly into a SQL query string. Must use parameterized queries."
  • Permission control: "Strictly forbid relying solely on role information passed from the frontend in API implementations. Must re-query and verify permissions on the backend based on the current user's session."
  1. The "bottleneck" of performance

These are critical for preventing your application from crashing as user count grows.

  • Database queries: "Strictly forbid executing database queries or API calls inside loops (N+1 problem)."
  • Memory usage: "Strictly forbid loading a potentially large file (like a user upload) or database query result entirely into memory at once. Must use streaming or pagination."
  1. The "contract" of team collaboration

These are the rules that ensure code style and quality remain consistent when multiple people (and multiple AIs) collaborate.

  • Code style: "Strictly forbid submitting any code that has not passed ESLint and Prettier checks." (This can become an automated script, but it is also useful as an AI constraint first.)
  • Test coverage: "Strictly forbid writing unit tests with less than 80% coverage for new business logic functions."

Once these "absolute red lines" are defined, they should be treated as "divine laws," written in the project documentation in the most eye-catching, unquestionable tone, and become the first line of defense when you review AI's code.

Step Two: Define "Core Implementation Processes"

With the red lines drawn, the remaining area is the "core implementation process" where we can confidently let AI leverage its powerful productivity. But this does not mean complete laissez-faire. Within this area, our constraints shift from "what is strictly forbidden" to more refined guidance on "how it should be done."

This guidance can still be constructed using the "negative space" approach. By excluding suboptimal solutions, we let AI choose the optimal one.

Scenario: Implementing a frontend search box with debounce.

  • Traditional "implementation instruction": "Please implement a search box with debounce." (AI might implement a crude debounce using setTimeout manually, or use a library you do not want.)
  • Negative space design "process guidance":
    1. Draw the red line: "Strictly forbid implementing debounce logic manually. Strictly forbid installing any utility library other than lodash-es."
    2. Guide the path: "Please implement a search box. When user input changes, it needs to call the api.search(term) function. This call needs to be debounced with a delay of 300 milliseconds. Must use the debounce function from the lodash-es library to accomplish this."

In this example:

  • "Strictly forbid manual implementation," "Strictly forbid installing other libraries" are boundaries, guarding the purity of code quality and project dependencies.
  • "Must use lodash-es's debounce" is the path, fixing the chosen tool and method within the boundary and eliminating forking of the solution.

Through this combination of "red line + path," we ensure architectural stability while fully leveraging AI's coding efficiency in the specific implementation layer. We have become a true "navigator," both delineating the impassable reef zone for the ship and marking the optimal route within safe waters.

4.3 Using the "Process of Elimination" to Guide AI Toward the Only Correct Path

We now have powerful weapons (prohibition instructions) and a clear map (capability boundaries). Next comes the tactical level -- how to flexibly use the "process of elimination" in daily development conversations, like a patient mentor, guiding the confused AI step by step toward the truth.

This process is like playing a game of "20 Questions." AI is the guesser, and you are the person who narrows down the range of guesses through a series of "yes/no" answers (in our scenario, "allow/prohibit").

The "Constraint Funnel" Model

Imagine a huge funnel. The widest opening of the funnel is AI's "infinite possibility space." The narrowest outlet of the funnel is the "single correct solution" we want. Each of our "prohibition instructions" adds a filter screen to the funnel wall; each interaction makes the funnel narrower.

First layer of filtration: Global constraints (document layer) This is the top, broadest layer of the funnel. It is constituted by our AGENTS.md and ARCHITECTURE.md. Before starting any work, AI must first pass through this layer of filtration.

You: "Starting new work. Please read the project documentation first." AI (internally): "OK, can't use any, can't fetch in components, database must be Postgres..." (At this point, trillions of wrong possibilities have been instantly eliminated.)

Second layer of filtration: Task-level constraints (initial instruction) Next, you give a specific task, accompanied by "partial" prohibition instructions for this task.

You: "We need to implement a user profile page. Prohibit putting all information in one component. Must split into three sub-components: AvatarCard, UserInfo, ActionButtons. Prohibit making any API requests inside the sub-components. All data must be injected by the parent component through props." AI (internally): "Understood. Cannot make a 'monster' component. Data flow must be unidirectional. Sub-components must be 'dumb'." (The funnel narrows sharply. Thousands of wrong practices for component design and data flow are eliminated.)

Third layer of filtration: Interactive fine-tuning (dynamic constraints in dialogue)

AI gives its first version of the code. You review it and discover new, subtler issues. At this point, you add a finer filter screen through dialogue.

AI: (Generated the code, but used window.confirm directly in the ActionButtons component for deletion confirmation.) You: "Good code structure. However, prohibit using any browser-native window.alert, window.confirm, or window.prompt in any component. Must use the Modal component from our UI library to interact with the user." AI (internally): "Understood. User interaction must be consistent. Cannot break UI consistency with native APIs." (The funnel narrows further. All implementation paths related to native popups are blocked.)

Fourth layer of filtration: Final acceptance (tests and automation) With the code basically taking shape, you bring out the final, most objective filter.

You: "The code looks good. Now, please write unit tests for the UserProfile parent component. Constraint: Test coverage must reach 90%. All API requests must be mocked. Prohibit making real network requests in tests." AI: (Writes tests. To meet coverage, it might discover some edge cases it had not considered in its own code and proactively fix them.) (The funnel outlet is extremely narrow now. Only logically rigorous, well-considered, and highly testable code can pass this last filter.)

Through this "constraint funnel" model, we decompose a complex, open-ended "creation" task into a series of simple, closed-ended "elimination" tasks. We no longer expect AI to get it right in one go. Instead, we enjoy the process of gradually sculpting a rough stone into a fine piece of art by continuously tightening constraints.

This process not only produces high-quality code, but more importantly, it keeps you -- the human developer -- firmly in control of the project's direction and final interpretation. You have become the ultimate judge who sets the rules, adjusts the funnel, and defines what is "beautiful" and "correct."

[Real-World Example] The Process of Writing a High-Quality Constraint List

Theory is over. Let us do a live-fire drill.

Mission objective: Develop a generic, reusable React data table component (DataTable).

This task is very classic. If thrown directly at AI, you might get a hard-to-maintain "monster" that mixes together various functions. Now, as a "constraint architect," we step in and use "negative space design" to lead the development.

Step One: Brainstorm, Think of All the Ways Things Could Go "Wrong"

Before writing any instructions, we think first. What are the common pitfalls and "bad smells" in the design and implementation of a DataTable component?

  1. Data and UI coupling: fetch logic hard-coded inside the component, making it non-reusable.
  2. State management chaos: Pagination, sorting, and filtering states scattered everywhere, hard to synchronize.
  3. Bloated rendering logic: In a giant render function, countless if-else statements handle different column types (text, image, button).
  4. Feature over-integration: Stuffing data fetching, rendering, pagination, sorting, filtering, exporting to Excel, and all other features into one component.
  5. Poor performance: When data volume is large, every re-render calculates all cells, causing lag.
  6. Terrible API design: Dozens of props controlling component behavior, hard to use and remember.

Good, we have identified this "minefield." Now, our job is to turn these "mines" into clear "prohibition instructions."

Step Two: Transform "Errors" into "Prohibition Instructions" and Build the Constraint List

Let us convert them one by one, following the principles of "precision, quantification, and providing examples" discussed earlier.

  1. For "Data and UI coupling":
  • Prohibition instruction: "The DataTable component must strictly contain no data fetching logic (like fetch, axios). It must be a pure 'dumb' component. All data must be passed in through a prop named data. Loading and error states must also be passed in from outside through isLoading and error props."
  1. For "State management chaos":
  • Prohibition instruction: "The DataTable component must strictly NOT manage pagination (currentPage), sorting (sortKey, sortOrder), or filtering (filters) states internally. These states must be controlled by the parent component and passed in through props. When the user performs operations like pagination or sorting, the component must notify the parent component of the new state object by calling the onStateChange callback prop."
  1. For "Bloated rendering logic":
  • Prohibition instruction: "Strictly forbid writing any if-else or switch statements inside the DataTable component to determine column types. Column definitions must be passed in through a prop named columns. columns is an array of objects, where each object can contain a render function for custom rendering of that column's cells.
  • Do (Good Practice):
const columns = [
{ key: 'name', title: 'Name' },
{ key: 'avatar', title: 'Avatar', render: (rowData) => <img src={rowData.avatarUrl} /> }
];
<DataTable columns={columns} ... />
  1. For "Feature over-integration":
  • Prohibition instruction: "The DataTable component's core responsibility is limited to rendering the table UI. Features like pagination, header sorting, and filter input boxes must be implemented as separate sub-components or external components, and used together with DataTable through React's composition pattern. Strictly forbid writing the pagination bar's HTML directly inside the DataTable component."
  1. For "Poor performance":
  • Prohibition instruction: "To optimize performance, the DataTable component must be wrapped with React.memo to prevent unnecessary re-renders. Additionally, the array passed to the columns prop, as well as the render functions within it, must be stabilized in the parent component using useMemo and useCallback."
  1. For "Terrible API design":
  • Prohibition instruction: "The number of props for the component must not exceed 8. Related props must be combined into objects. For example, all pagination-related props (currentPage, pageSize, totalItems) must be merged into a single pagination object prop."

Step Three: Integrate the List, Form the Final "Genesis Instruction"

Now, we integrate all the above constraints into a clear, structured final instruction for AI.

Task: Create a highly reusable, high-performance React DataTable component.

Please strictly adhere to all the following constraints and design principles. These are absolute and unquestionable.

  1. Architecture & Data Flow Constraints
  • [P-1.1] Strictly do not include any data fetching logic (fetch, axios) inside the component. DataTable must be a pure, controlled "dumb" component.
  • [P-1.2] Strictly do not manage any persistent state (e.g., pagination, sorting, filtering) inside the component. All state must be passed in from the parent component via props, and user operations must be communicated out via the onStateChange callback function.
  1. Props API Design Constraints
  • [P-2.1] Must receive the data array to render via a prop named data: any[].
  • [P-2.2] Must receive column definitions via a prop named columns: ColumnDef[]. The column definition interface must support an optional render: (row: any) => React.ReactNode function for custom cell rendering.
  • [P-2.3] Strictly do not use more than 8 top-level props. Related props must be combined into objects (e.g., pagination: { currentPage, totalItems }).
  1. Features & Responsibilities Constraints
  • [P-3.1] Strictly do not hard-code the implementation of UI controls like pagination or search box inside the DataTable component. The component's core responsibility is limited to rendering the <table> structure. These features should be implemented externally through composition patterns.
  1. Performance Constraints
  • [P-4.1] Must wrap the DataTable component's export with React.memo.

Based on all the above constraints, please generate the initial code for the src/components/DataTable.tsx file, including the necessary TypeScript type definitions (e.g., ColumnDef).

Do you see it? This instruction hardly tells AI "how to implement." It is full of "don't do this, must do that." It is an architecture blueprint, a legal document, a quality assurance certificate.

When you hand such an instruction to AI, you are no longer a supplicant but a commander issuing orders. The code AI generates transforms from an uncertain "draft" that requires your mental effort to review and modify, into a highly determined "semi-finished product" that basically matches your ideal blueprint in your mind.

This is the power of "negative space design." It liberates you from the endless tug-of-war with AI and returns you to the core value of a software engineer: thinking, designing, defining rules.

From the next chapter, we will learn how to continue applying and expanding our constraint capabilities in more dynamic and uncontrollable "processes" and "environments."