Skip to content

Instantly share code, notes, and snippets.

@scottcorgan
Created March 7, 2026 12:32
Show Gist options
  • Select an option

  • Save scottcorgan/39cd369451bfe8c11ed62a511b9b9426 to your computer and use it in GitHub Desktop.

Select an option

Save scottcorgan/39cd369451bfe8c11ed62a511b9b9426 to your computer and use it in GitHub Desktop.
Should code be written for LLMs instead of humans? Two implementations of the same React component compared — optimized for both readability and changeability.
import { useState, useCallback, useRef, type ReactNode, type CSSProperties } from "react";
type Column<T> = {
key: keyof T & string;
label: string;
width?: number;
render?: (value: T[keyof T], row: T) => ReactNode;
};
type Props<T> = {
columns: Column<T>[];
data: T[];
minColumnWidth?: number;
};
const styles = {
wrapper: { overflowX: "auto" } satisfies CSSProperties,
table: {
borderCollapse: "collapse",
tableLayout: "fixed",
width: "max-content",
} satisfies CSSProperties,
th: {
position: "relative",
padding: "8px 12px",
borderBottom: "2px solid #e2e8f0",
textAlign: "left",
fontWeight: 600,
fontSize: 14,
userSelect: "none",
background: "#f8fafc",
} satisfies CSSProperties,
td: {
padding: "8px 12px",
borderBottom: "1px solid #e2e8f0",
fontSize: 14,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
} satisfies CSSProperties,
handle: {
position: "absolute",
right: 0,
top: 0,
bottom: 0,
width: 6,
cursor: "col-resize",
background: "transparent",
} satisfies CSSProperties,
};
function useColumnWidths<T>(columns: Column<T>[], minWidth = 50) {
const [widths, setWidths] = useState(() =>
Object.fromEntries(columns.map((col) => [col.key, col.width ?? 150]))
);
const resize = useCallback(
(key: string, delta: number) => {
setWidths((prev) => ({
...prev,
[key]: Math.max(minWidth, (prev[key] ?? 150) + delta),
}));
},
[minWidth]
);
return { widths, resize } as const;
}
function ResizeHandle({ onResize }: { onResize: (delta: number) => void }) {
const startX = useRef(0);
const onMouseDown = useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
startX.current = e.clientX;
const onMouseMove = (e: MouseEvent) => {
onResize(e.clientX - startX.current);
startX.current = e.clientX;
};
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
},
[onResize]
);
return (
<div
onMouseDown={onMouseDown}
style={styles.handle}
role="separator"
aria-orientation="vertical"
/>
);
}
function HeaderCell<T>({
column,
width,
onResize,
}: {
column: Column<T>;
width: number;
onResize: (delta: number) => void;
}) {
return (
<th style={{ ...styles.th, width }}>
{column.label}
<ResizeHandle onResize={onResize} />
</th>
);
}
function DataCell<T>({
column,
row,
width,
}: {
column: Column<T>;
row: T;
width: number;
}) {
const content = column.render
? column.render(row[column.key], row)
: String(row[column.key] ?? "");
return <td style={{ ...styles.td, width }}>{content}</td>;
}
export default function ResizableTable<T extends Record<string, unknown>>({
columns,
data,
minColumnWidth,
}: Props<T>) {
const { widths, resize } = useColumnWidths(columns, minColumnWidth);
return (
<div style={styles.wrapper}>
<table style={styles.table}>
<thead>
<tr>
{columns.map((col) => (
<HeaderCell
key={col.key}
column={col}
width={widths[col.key]}
onResize={(delta) => resize(col.key, delta)}
/>
))}
</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr key={i}>
{columns.map((col) => (
<DataCell
key={col.key}
column={col}
row={row}
width={widths[col.key]}
/>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}

Should Code Be Written for LLMs Instead of Humans?

Two implementations of the exact same React component — a table with resizable columns — written in two radically different styles. Both optimized not just for reading, but for changing.

The Files

  • human-friendly-ResizableTable.tsx (~155 lines) — Optimized for human readability and human changeability
  • llm-friendly-ResizableTable.tsx (~240 lines) — Optimized for LLM comprehension and LLM changeability

Both produce identical runtime behavior. The difference is entirely in how the code communicates its intent and how easy it is to modify.


Axis 1: Readability

Dimension Human-Friendly LLM-Friendly Why It Matters for LLMs
Variable names col, widths, delta columnDefinition, columnWidthsByColumnKey, horizontalDragDeltaInPixels LLMs don't have "muscle memory" for abbreviations. delta could mean anything — time, position, diff. The verbose name eliminates disambiguation entirely.
Magic numbers 50, 150, 6 inline tableStyleConfig.columnMinimumWidthInPixels, etc. An LLM can't "see" that 50 in a Math.max is a minimum width constraint. The constant name is the documentation.
Type names Column<T>, Props<T> ResizableTableColumnDefinition<TRowData> Column is ambiguous across any codebase. The full name tells the LLM exactly what domain this type belongs to without needing file-level context.
JSDoc None On every type, function, and component LLMs process doc comments as first-class semantic context. The @property and @example annotations create an explicit mapping between field names and their purpose that an LLM can reference during reasoning.
Function extraction Logic inline in hooks Named pure functions like buildHeaderCellStyle, createMouseDownHandlerForColumnResize Named functions give the LLM a grep-friendly handle. If asked "how is width clamped?", the LLM can locate the logic by name rather than reasoning about anonymous arrow functions.
Callback naming onMouseDown, onMouseMove handleMouseMoveDuringDrag, handleMouseUpToEndDrag The human version relies on closure scope to know which mousedown handler this is. The LLM version encodes the full context chain in the name itself.

Axis 2: Changeability

This is the more interesting axis. "Easy to read" and "easy to change" optimize for different things.

Human Changeability Strategy: Composition + DRY

The human-friendly version uses small, composable pieces with shared abstractions:

  • Shared styles object — change a color once and it propagates everywhere
  • Extracted sub-components (ResizeHandle, HeaderCell, DataCell) — swap one implementation without touching others
  • Extracted hook (useColumnWidths) — change state management without touching rendering
  • Narrow prop interfaces — each component declares exactly what it needs

When a human wants to change the header background, they update styles.th.background. When they want a different resize behavior, they modify ResizeHandle in isolation. Their IDE helps them navigate between pieces.

This works for humans because:

  • Humans navigate code spatially with IDE features (go-to-definition, find-references)
  • Humans build mental maps of component relationships over time
  • Humans benefit from DRY because "change in one place" aligns with how they think

LLM Changeability Strategy: Colocation + Configuration + Duplication

The LLM-friendly version uses the opposite approach:

  • Single flat component, no sub-components — the LLM never has to reason across component boundaries or trace props through a tree
  • Top-level tableStyleConfig object — every visual tunable in one named, documented block. "Change the header color" becomes "find headerCellBackgroundColor in the config object"
  • Section markers (// --- STATE: column widths ---, // --- RENDER: header row ---) — explicit anchors the LLM can search for, acting as a table of contents
  • No shared style abstractionsbuildHeaderCellStyle and buildBodyCellStyle are separate functions that each read from the config independently. Changing how body cells look can never accidentally break headers.
  • Named intermediates at every step — when the LLM needs to insert logic between steps A and B, having const headerCellStyle = ... and const onMouseDownForThisColumn = ... as explicit variables makes insertion trivial
  • @example blocks on types — show the LLM exactly what a valid modification looks like. Training signal for what the shape of a change should be.

This works for LLMs because:

  • LLMs process files linearly with no IDE features, no go-to-definition, no spatial memory
  • LLMs reason better with fewer files and less indirection — colocating everything removes an entire class of errors (wrong file, wrong component, missed a reference)
  • LLMs benefit from duplication because isolated code means changes have local-only blast radius. DRY is an enemy here: a shared styles.td used in two places means the LLM must reason about whether changing it breaks the other consumer.
  • Section markers function like ctrl+F for an LLM — they're semantic anchors in a flat token stream

Side-by-Side: "Change the header background color"

Human version — 2 edits, needs mental model of where styles live:

  1. Find the styles object (could be in this file, could be imported)
  2. Change styles.th.background from "#f8fafc" to "#1e293b"

LLM version — 1 edit, self-evident from the config block:

  1. Change tableStyleConfig.headerCellBackgroundColor from "#f8fafc" to "#1e293b"

Side-by-Side: "Add a hover effect to body rows"

Human version — navigate component tree:

  1. Understand that DataCell renders individual cells, not rows
  2. Realize the <tr> is in the parent ResizableTable, not in DataCell
  3. Add hover state to the right component at the right level

LLM version — search for section marker:

  1. Find // --- RENDER: body rows ---
  2. Add onMouseEnter/onMouseLeave to the <tr> that's right there
  3. Add a hover color to tableStyleConfig

The Core Argument

Humans read code spatially — they scan structure, recognize shapes, and fill in gaps from experience. A senior React dev sees useRef(0), onMouseDown, and delta, and instantly knows "ah, this is a drag handler tracking horizontal offset." Concise code respects their limited visual bandwidth and rewards pattern recognition. Composition respects how they navigate with IDE tools.

LLMs read code linearly and literally — every token is weighted equally in the attention mechanism. They have no spatial intuition, no IDE, no pattern library built from years of coding, and no ability to "just know" that delta in this particular closure means horizontal pixel offset from the last mouse event. Flat, colocated, explicitly-named code means less reasoning, fewer inference steps, and a smaller blast radius for any change.

The Changeability Paradox

The principles that make code easy for humans to change — DRY, composition, abstraction — make it harder for LLMs to change, because every abstraction is an indirection the LLM must resolve.

The principles that make code easy for LLMs to change — duplication, colocation, flat structure — make it harder for humans to change, because humans see boilerplate and inconsistency risk.

Specific LLM Advantages

  1. Configuration objects are edit magnets. The tableStyleConfig block is an explicit, documented "here's what you can change" surface. An LLM asked to "make the table more compact" can scan config keys and adjust cellPaddingVertical and cellPaddingHorizontal without reading any JSX.

  2. Section markers are grep targets. // --- RENDER: header row --- lets the LLM jump to exactly the right spot in a flat file. Sub-components require the LLM to first figure out which component to edit.

  3. Duplication eliminates cross-reference reasoning. When header and body cell styles are independent functions, the LLM can modify one without loading the other into its context window. Shared styles require reading all consumers.

  4. @example blocks are change templates. When an LLM sees @example { columnKey: "status", renderCellContent: ... }, it has a concrete template for how to add a new custom-rendered column. It doesn't need to infer the pattern from the implementation.

  5. Flat components mean single-file edits. The LLM version requires editing exactly one function in one file for any change. The human version might require coordinating edits across ResizeHandle, HeaderCell, DataCell, and the parent component.


The Trade-Off

The LLM-friendly version is ~1.5x longer but contains ~zero ambiguity, has a single edit target for any change, and makes every tunable value discoverable from the config block.

Human-friendly code trusts the reader's brain and IDE to fill gaps. LLM-friendly code fills them all in advance.

As AI-assisted development becomes the norm, the question isn't whether this trade-off exists — it's where the balance point should be.

import { useState, useCallback, useRef, type ReactNode, type CSSProperties } from "react";
// ============================================================================
// CONFIGURATION
//
// Every tunable value in this component lives here. To change appearance,
// behavior, or constraints, edit this object — nothing else needs to change.
//
// Common modifications:
// - Change header color → tableStyleConfig.headerCellBackgroundColor
// - Change cell padding → tableStyleConfig.cellPaddingVertical / cellPaddingHorizontal
// - Change min col width → tableStyleConfig.columnMinimumWidthInPixels
// - Change border style → tableStyleConfig.headerBorderBottom / bodyRowBorderBottom
// - Change font size → tableStyleConfig.headerFontSizeInPixels / bodyCellFontSizeInPixels
// ============================================================================
const tableStyleConfig = {
columnDefaultWidthInPixels: 150,
columnMinimumWidthInPixels: 50,
resizeHandleHitAreaWidthInPixels: 6,
headerCellBackgroundColor: "#f8fafc",
headerCellFontWeight: 600,
headerFontSizeInPixels: 14,
headerBorderBottom: "2px solid #e2e8f0",
bodyCellFontSizeInPixels: 14,
bodyRowBorderBottom: "1px solid #e2e8f0",
cellPaddingVertical: 8,
cellPaddingHorizontal: 12,
} as const;
// ============================================================================
// TYPES
// ============================================================================
/**
* Defines one column in the resizable table.
*
* @template TRowData - The shape of each row object in the dataset.
*
* @property columnKey - Must match a key in TRowData. Used to look up cell
* values and track column width state.
* @property headerLabel - Text shown in the column header.
* @property initialWidthInPixels - Optional starting width. Falls back to
* tableStyleConfig.columnDefaultWidthInPixels.
* @property renderCellContent - Optional custom cell renderer. When omitted,
* the raw cell value is converted to a string via String().
*
* @example
* // Plain text column
* { columnKey: "name", headerLabel: "Full Name", initialWidthInPixels: 200 }
*
* @example
* // Custom rendered column
* {
* columnKey: "status",
* headerLabel: "Status",
* renderCellContent: (cellValue) => <StatusBadge status={String(cellValue)} />,
* }
*/
type ResizableTableColumnDefinition<TRowData> = {
columnKey: keyof TRowData & string;
headerLabel: string;
initialWidthInPixels?: number;
renderCellContent?: (
cellValue: TRowData[keyof TRowData],
entireRow: TRowData
) => ReactNode;
};
/**
* Props for the ResizableTable component.
*
* @property columnDefinitions - Columns to render, in left-to-right order.
* @property tableData - Row objects to display.
* @property extractStableRowKey - Returns a unique string key for each row.
* Used as the React key on <tr> elements. Avoids index-as-key issues when
* rows are reordered, inserted, or removed.
*
* @example
* <ResizableTable
* columnDefinitions={[
* { columnKey: "name", headerLabel: "Name" },
* { columnKey: "email", headerLabel: "Email", initialWidthInPixels: 250 },
* ]}
* tableData={[
* { id: "1", name: "Alice", email: "alice@example.com" },
* { id: "2", name: "Bob", email: "bob@example.com" },
* ]}
* extractStableRowKey={(row) => String(row.id)}
* />
*/
type ResizableTableProps<TRowData> = {
columnDefinitions: ResizableTableColumnDefinition<TRowData>[];
tableData: TRowData[];
extractStableRowKey: (row: TRowData, zeroBasedIndex: number) => string;
};
type ColumnWidthsByColumnKey = Record<string, number>;
// ============================================================================
// COMPONENT
//
// This is intentionally a single flat component with no sub-components.
// All rendering, state, and event handling is colocated so that any change
// requires editing only this one function. Section markers below act as
// anchors for locating specific concerns.
// ============================================================================
/**
* Renders a data table with user-resizable columns via drag handles on
* header cell borders.
*
* Architecture: single component, no sub-components, no extracted hooks.
* Every concern is colocated and marked with section comments so that
* modifications only require understanding this one function.
*/
export default function ResizableTable<
TRowData extends Record<string, unknown>,
>({
columnDefinitions,
tableData,
extractStableRowKey,
}: ResizableTableProps<TRowData>): ReactNode {
// --- STATE: column widths ---
const [columnWidthsByColumnKey, setColumnWidthsByColumnKey] =
useState<ColumnWidthsByColumnKey>(() => {
const initialWidths: ColumnWidthsByColumnKey = {};
for (const columnDef of columnDefinitions) {
initialWidths[columnDef.columnKey] =
columnDef.initialWidthInPixels ??
tableStyleConfig.columnDefaultWidthInPixels;
}
return initialWidths;
});
// --- BEHAVIOR: apply resize delta to a column ---
const applyResizeDeltaToColumn = useCallback(
(columnKey: string, horizontalDragDeltaInPixels: number): void => {
setColumnWidthsByColumnKey((previousWidths) => {
const previousWidthForThisColumn =
previousWidths[columnKey] ?? tableStyleConfig.columnDefaultWidthInPixels;
const newWidthBeforeClamping =
previousWidthForThisColumn + horizontalDragDeltaInPixels;
const newWidthAfterClamping = Math.max(
tableStyleConfig.columnMinimumWidthInPixels,
newWidthBeforeClamping
);
return { ...previousWidths, [columnKey]: newWidthAfterClamping };
});
},
[]
);
// --- BEHAVIOR: mouse-drag resize handler factory ---
const lastMouseXDuringDrag = useRef<number>(0);
const createMouseDownHandlerForColumnResize = useCallback(
(columnKey: string) => {
return (mouseDownEvent: React.MouseEvent): void => {
mouseDownEvent.preventDefault();
lastMouseXDuringDrag.current = mouseDownEvent.clientX;
const handleMouseMoveDuringDrag = (mouseMoveEvent: MouseEvent): void => {
const pixelsSinceLastEvent =
mouseMoveEvent.clientX - lastMouseXDuringDrag.current;
applyResizeDeltaToColumn(columnKey, pixelsSinceLastEvent);
lastMouseXDuringDrag.current = mouseMoveEvent.clientX;
};
const handleMouseUpToEndDrag = (): void => {
document.removeEventListener("mousemove", handleMouseMoveDuringDrag);
document.removeEventListener("mouseup", handleMouseUpToEndDrag);
};
document.addEventListener("mousemove", handleMouseMoveDuringDrag);
document.addEventListener("mouseup", handleMouseUpToEndDrag);
};
},
[applyResizeDeltaToColumn]
);
// --- STYLES: computed per-element (not shared, safe to edit independently) ---
const cellPadding =
`${tableStyleConfig.cellPaddingVertical}px ${tableStyleConfig.cellPaddingHorizontal}px`;
function buildHeaderCellStyle(columnKey: string): CSSProperties {
return {
width: columnWidthsByColumnKey[columnKey],
position: "relative",
padding: cellPadding,
borderBottom: tableStyleConfig.headerBorderBottom,
textAlign: "left",
fontWeight: tableStyleConfig.headerCellFontWeight,
fontSize: tableStyleConfig.headerFontSizeInPixels,
userSelect: "none",
background: tableStyleConfig.headerCellBackgroundColor,
};
}
function buildBodyCellStyle(columnKey: string): CSSProperties {
return {
width: columnWidthsByColumnKey[columnKey],
padding: cellPadding,
borderBottom: tableStyleConfig.bodyRowBorderBottom,
fontSize: tableStyleConfig.bodyCellFontSizeInPixels,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
};
}
const resizeHandleStyle: CSSProperties = {
position: "absolute",
right: 0,
top: 0,
bottom: 0,
width: tableStyleConfig.resizeHandleHitAreaWidthInPixels,
cursor: "col-resize",
background: "transparent",
};
// --- RENDER: table ---
return (
<div style={{ overflowX: "auto" }}>
<table style={{ borderCollapse: "collapse", tableLayout: "fixed", width: "max-content" }}>
{/* --- RENDER: header row --- */}
<thead>
<tr>
{columnDefinitions.map((columnDef) => {
const headerCellStyle = buildHeaderCellStyle(columnDef.columnKey);
const onMouseDownForThisColumn =
createMouseDownHandlerForColumnResize(columnDef.columnKey);
return (
<th key={columnDef.columnKey} style={headerCellStyle}>
{columnDef.headerLabel}
<div
onMouseDown={onMouseDownForThisColumn}
style={resizeHandleStyle}
role="separator"
aria-orientation="vertical"
/>
</th>
);
})}
</tr>
</thead>
{/* --- RENDER: body rows --- */}
<tbody>
{tableData.map((singleRowData, zeroBasedRowIndex) => {
const rowKey = extractStableRowKey(singleRowData, zeroBasedRowIndex);
return (
<tr key={rowKey}>
{columnDefinitions.map((columnDef) => {
const rawCellValue = singleRowData[columnDef.columnKey];
const bodyCellStyle = buildBodyCellStyle(columnDef.columnKey);
const hasCustomRenderer = columnDef.renderCellContent != null;
const renderedCellContent = hasCustomRenderer
? columnDef.renderCellContent(rawCellValue, singleRowData)
: String(rawCellValue ?? "");
return (
<td key={columnDef.columnKey} style={bodyCellStyle}>
{renderedCellContent}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment