Reducing Cognitive Load Through Technology Constraints: Our Swift-Only Journey
The notification arrives at 3 PM on Friday: “The customer portal is down, and we need to fix the authentication bug before the weekend.” You open your laptop, and immediately your brain starts loading context. What language is this service written in? What database? Which cloud provider? What about the cache layer?
Before you’ve even looked at the actual bug, your working memory is already filling up with infrastructure decisions, language quirks, and deployment contexts. This is cognitive load in action, and it’s costing your team time, money, and mental energy.
What is Cognitive Load?
According to research by Ruslan Zakirullin, cognitive load is “how much a developer needs to think in order to complete a task.” The human brain can hold roughly four chunks of information in working memory at once. When cognitive load exceeds this threshold, confusion sets in - and confusion costs us dearly.
Most cognitive load in software development isn’t intrinsic to the business problem we’re solving. Instead, it’s what researchers call “extraneous cognitive load” - mental overhead created by the way information is presented, by architectural choices, and by the accumulated complexity of our technical decisions.
We’ve spent the past year deliberately reducing this extraneous load in our legal technology stack. Our approach: embrace constraints as a liberating force.
The Swift Everywhere Decision
Eighteen months ago, our team made what seemed like a radical choice: we would build everything in Swift. Not just iOS apps - everything. Our web servers, command-line tools, infrastructure scripts, and database migrations would all be written in one language.
This wasn’t about Swift evangelism. It was about cognitive load reduction.
Here’s what this looks like in practice. When I need to fix that authentication bug, I know exactly what I’m walking into:
public final class User: Model, Content, Authenticatable, @unchecked Sendable {
public static let schema = "users"
@Field(key: "role")
public var role: UserRole
public func hasRole(_ requiredRole: UserRole) -> Bool {
role.accessLevel >= requiredRole.accessLevel
}
}
This User model works seamlessly across our server (Vapor), our iOS app, and our command-line tools. Same syntax, same patterns, same mental model. The cognitive load is: 🧠+ (just loading Swift into working memory).
Compare this with a polyglot architecture where the same concept might be expressed differently across layers:
Different interfaces in the frontend
Different structs on the backend
Different data classes in CLI tools
Different validation patterns in each layer
That’s 🧠++++ before we’ve even started debugging.
The PostgreSQL-Only Philosophy
Our database decision followed the same principle. Instead of optimizing for theoretical perfect tool matching, we chose to use PostgreSQL for everything:
User data and authentication
Full-text search (PostgreSQL’s built-in capabilities)
Time-series metrics (PostgreSQL with proper indexing)
JSON document storage (PostgreSQL’s JSONB)
Geospatial queries (PostGIS extension)
This choice eliminates entire classes of cognitive overhead. We don’t context-switch between SQL and NoSQL query patterns. We don’t maintain connection pools to multiple database systems. We don’t debug replication lag between different data stores.
When our newsletter analytics service needs to calculate open rates, it’s straightforward Swift + PostgreSQL:
struct NewsletterAnalyticsService {
let database: Database
func calculateOpenRate(for campaign: Newsletter) async throws -> Double {
// Single query, single database, consistent patterns
let opens = try await NewsletterEvent.query(on: database)
.filter(\.$newsletter.$id == campaign.id)
.filter(\.$eventType == .opened)
.count()
return Double(opens) / Double(campaign.recipientCount)
}
}
No need to remember complex query syntaxes or different data access patterns. Just Swift and Postgres SQL - cognitive load stays at 🧠++.
AWS-Only Infrastructure
We extended the same constraint-based thinking to our cloud infrastructure. Everything runs on AWS:
Static sites on S3 + CloudFront
Containers on ECS
Databases on RDS PostgreSQL
Object storage on S3
Email via SES
Monitoring with CloudWatch
Our Swift-based deployment tool, Brochure, leverages this consistency:
struct S3Uploader {
let client: S3Client
func upload(_ file: URL, to bucket: String, key: String) async throws {
let body = try Data(contentsOf: file)
let request = PutObjectRequest(
bucket: bucket,
body: .data(body),
key: key,
contentType: ContentTypeDetector.detect(for: file)
)
_ = try await client.putObject(input: request)
}
}
Same SDK patterns across all our infrastructure. Same IAM concepts. Same monitoring approach. When something breaks at 3 AM, there’s no question about which cloud provider’s documentation to consult.
Monorepo Benefits
Our constraints extend to code organization. Everything lives in a single repository with shared targets:
Dali: Shared models and business logicBazaar: Main web API serverConcierge: iOS client applicationVegas: Infrastructure-as-code in SwiftPalette: Database migrations and seeding
This structure means when we need to add a new field to our User model, the change propagates consistently:
Update the model in
Dali/User.swiftAdd the database migration in
Palette/Migrations/The API, iOS app, and CLI tools automatically get the updated type
No version mismatches between packages. No protocol buffer compilation steps. No keeping multiple language implementations in sync. The Swift compiler ensures consistency across our entire stack.
Real-World Impact
After one year of this constraint-based approach, we’ve measured specific improvements:
Developer Onboarding: New developers contribute meaningful code within their first day. Previously, it took a week just to understand our polyglot architecture.
Context Switching: Elimination of language-specific context switching reduced the time to fix bugs by approximately 40%. Developers no longer need to reload mental models for different parts of the system.
Testing Consistency: Our Swift Testing framework works identically across all targets:
@Suite("User Service Tests", .serialized)
struct UserServiceTests {
@Test("UserService can prepare user profile")
func userServiceCanPrepareUserProfile() async throws {
try await TestUtilities.withApp { app, database in
let service = UserService(database: database)
// Same testing patterns everywhere
let result = try await service.prepareUserProfile(user: testUser)
#expect(result.person.name == "Expected Name")
}
}
}
Same syntax for testing server logic, iOS UI components, and CLI tools. Cognitive load for writing tests: 🧠++ regardless of which part of the system we’re testing.
Error Reduction: Eliminating language boundaries reduced integration bugs by 60%. Type mismatches that previously only surfaced at runtime now cause compile-time errors.
The Tradeoffs
These constraints aren’t without costs. We acknowledge the tradeoffs we’ve made:
Performance: We’re not using the theoretical “best” tool for every job. Our PostgreSQL full-text search isn’t as sophisticated as specialized search engines could be. However, Swift’s performance characteristics are excellent for our use cases, with compile-time optimizations and efficient memory management.
Ecosystem: Some Swift server-side libraries are less mature than their equivalents in more established server ecosystems. We occasionally build functionality that exists off-the-shelf in other ecosystems, but this gives us deeper understanding of our own codebase.
Team Skills: Our hiring is constrained to developers comfortable with Swift (though we’ve found this constraint actually improves team coherence).
The question isn’t whether these tradeoffs exist - they do. The question is whether the cognitive load reduction justifies them. For our team, emphatically yes.
When Constraints Work
Our approach works well because we prioritized developer productivity over theoretical optimization. We’re building legal software, not a trading system where microseconds matter. The business value comes from shipping reliable features quickly, not from squeezing maximum performance from every component.
Constraints work best when:
Team size is manageable: With 5 developers, coordination costs dominate raw performance
Domain complexity is high: Legal compliance has enough intrinsic complexity without adding technological complexity
Reliability matters more than optimization: Our users prefer consistent uptime over maximum throughput
Practical Application
If you’re considering similar constraints for your team, start small:
Pick one constraint: Maybe it’s “one language everywhere” or “PostgreSQL for all data”
Measure the current cost: How much time does your team spend context switching?
Build consensus: Constraints only work if the whole team commits
Start with new projects: Don’t rewrite everything at once
The goal isn’t to eliminate all technology choices - it’s to eliminate choices that create extraneous cognitive load without delivering proportional business value.
Testing Our Assumptions
We continue to measure and validate our constraint-based approach. Monthly retrospectives include specific discussion of cognitive load: Are there patterns that consistently confuse team members? Are there places where constraints hurt more than they help?
So far, the data supports our approach. Developer satisfaction is up, bug rates are down, and feature velocity has increased 35% year-over-year.
Looking Forward
Constraints aren’t limitations - they’re liberating forces that free mental capacity for the problems that actually matter. By choosing Swift everywhere, PostgreSQL for all data, AWS for all infrastructure, and a monorepo for all code, we’ve eliminated countless decisions that don’t directly serve our users.
The next time you’re debugging at 3 PM on a Friday, ask yourself: How much of your cognitive load is solving the customer’s problem, and how much is just managing the complexity of your architectural choices?
Sometimes the smartest technical decision is the one that lets you think less about technology and more about the problem you’re actually trying to solve.
Ready to Experience the Benefits?
Join thousands of satisfied customers who have made the switch to physical address services. Our Nevada-based service offers all these benefits and more for just $49 per month.