Alignment and Visual Patterns
The human brain is excellent at pattern recognition. Good formatting exploits this—making differences visible and similarities obvious.
When Alignment Helps
Tabular Data
When you have parallel structure, alignment shows it:
// Without alignment: Hard to scan
const routes = [
{ path: '/', component: Home, exact: true },
{ path: '/about', component: About, exact: true },
{ path: '/products', component: ProductList, exact: false },
{ path: '/products/:id', component: ProductDetail, exact: true },
{ path: '/cart', component: ShoppingCart, exact: true },
];
// With alignment: Pattern is visible
const routes = [
{ path: '/', component: Home, exact: true },
{ path: '/about', component: About, exact: true },
{ path: '/products', component: ProductList, exact: false },
{ path: '/products/:id', component: ProductDetail, exact: true },
{ path: '/cart', component: ShoppingCart, exact: true },
];
Now you can quickly scan a single column.
Configuration Objects
// Aligned for scannability
const config = {
host: 'localhost',
port: 3000,
timeout: 5000,
retries: 3,
debug: true,
};
Multiple Assignments
// When variables have conceptual relationship
const minValue = 0;
const maxValue = 100;
const stepValue = 10;
const startDate = new Date('2024-01-01');
const endDate = new Date('2024-12-31');
When Alignment Hurts
False Relationships
Don't align things that aren't related:
// BAD: Suggests relationship that doesn't exist
const userName = 'Alice';
const count = 42;
const isEnabled = true;
// GOOD: No false relationship implied
const userName = 'Alice';
const count = 42;
const isEnabled = true;
Maintenance Burden
Alignment requires maintenance. When one value changes, you might need to re-align everything:
// If you add a long key, you have to re-align all lines
const config = {
host: 'localhost',
port: 3000,
maximumConnectionPoolSize: 20, // New key forces re-alignment
timeout: 5000,
retries: 3,
};
Most teams skip alignment for this reason. If you use it, be prepared to maintain it—or use a formatter that handles it automatically.
Consistent Braces
Pick a brace style and use it everywhere.
K&R Style (Kernighan & Ritchie)
Opening brace on the same line:
function doSomething() {
if (condition) {
// ...
} else {
// ...
}
}
Allman Style
Opening brace on its own line:
function doSomething()
{
if (condition)
{
// ...
}
else
{
// ...
}
}
Which to Choose?
In JavaScript/TypeScript, K&R is dominant. In C#, Allman is common. Follow your language's conventions—and then use a formatter to enforce it.
The worst option is mixing styles:
// BAD: Mixed styles
function doSomething() {
if (condition)
{
// ...
} else {
// ...
}
}
Make Similar Code Look Similar
When you have similar operations, format them identically:
// BAD: Similar operations look different
const firstName = user.firstName.trim();
const lastName = user
.lastName
.trim();
const email = user.email.toLowerCase().trim();
// GOOD: Pattern is obvious
const firstName = user.firstName.trim();
const lastName = user.lastName.trim();
const email = user.email.toLowerCase().trim();
Parallel Structure
When code is conceptually parallel, make it visually parallel:
// Before: Hard to see the pattern
async function loadData() {
const users = await fetchUsers();
const products = await fetchProducts();
const ordersData = await getOrders();
const categories = await loadCategories();
}
// After: Pattern is clear
async function loadData() {
const users = await fetchUsers();
const products = await fetchProducts();
const orders = await fetchOrders();
const categories = await fetchCategories();
}
Notice:
- Consistent naming pattern (
fetchorload, not mixed) - Aligned equals signs
- Each line has the same structure
Highlighting Differences
When code has small differences, make the differences pop:
// BAD: Differences hidden in similar-looking code
const adminPermissions = ['read', 'write', 'delete', 'admin'];
const editorPermissions = ['read', 'write'];
const viewerPermissions = ['read'];
// GOOD: Structure highlights what varies
const permissions = {
admin: ['read', 'write', 'delete', 'admin'],
editor: ['read', 'write'],
viewer: ['read'],
};
Now you can immediately see what each role can do.
Visual Weight
Important things should have visual weight:
// BAD: Everything looks the same
const a = 1;
const veryImportantConfiguration = 'critical';
const b = 2;
// GOOD: Important things stand out
const a = 1;
const b = 2;
const VERY_IMPORTANT_CONFIGURATION = 'critical';
Use naming conventions (CAPS for constants), blank lines, and structure to draw attention to important code.
Indentation
Consistent indentation shows structure:
// BAD: Inconsistent indentation
function process(data) {
if (data.valid) {
const result = transform(data);
return result;
}
return null;
}
// GOOD: Structure is clear
function process(data) {
if (data.valid) {
const result = transform(data);
return result;
}
return null;
}
Indentation Size
2 spaces, 4 spaces, or tabs—it doesn't matter as long as it's consistent. Configure your formatter and forget about it.
The Trade-off with Formatters
Modern formatters like Prettier don't support alignment. They optimize for consistency and simplicity, not visual alignment.
This is a trade-off:
- With formatter: Perfectly consistent, zero effort, no alignment
- Without formatter: Manual effort, possible inconsistency, custom alignment possible
For most teams, the formatter is the right choice. Alignment is nice, but consistency is essential.
Key insight: Visual patterns help the brain parse code faster. Align similar things to show relationships. Use consistent braces and indentation. But don't over-align—it creates maintenance burden and can suggest false relationships. When in doubt, use a formatter.