Bill MacKenty
Home Computing Teaching Bushcraft Games Writing About
What is complexity?
In the context of Solving Complex Through Programming, what does complexity mean? How should we think about complexity?
Complex problems in programming rarely emerge from a single source. In many introductory programming courses, students initially encounter problems that are relatively constrained: a single script, a small dataset, or a clearly defined computational task. Under these conditions, it is possible to develop the impression that programming is primarily a matter of syntax, logic, and persistence. However, as systems increase in scale and sophistication, different forms of complexity begin to emerge simultaneously. Understanding these forms of complexity is essential for students attempting to solve substantial problems through programming.
Within this course, complexity is approached through at least three interconnected dimensions: algorithmic complexity, systemic complexity, and social complexity. Each dimension introduces distinct challenges, requires different modes of thinking, and demands increasing levels of nuance and discernment from the programmer.
Algorithmic Complexity
Algorithmic complexity concerns the relationship between a problem and the computational resources required to solve it. In practice, this usually involves examining how execution time, memory usage, or processing requirements increase as the size of the input increases.
One of the most important realizations for developing programmers is that a solution which appears successful at a small scale may become entirely impractical at a larger one. A program that performs adequately when processing one hundred records may fail when processing one hundred thousand. The issue is not necessarily that the code is incorrect, but that the underlying computational strategy does not scale effectively.
For this reason, computer scientists study concepts such as:
- time complexity
- space complexity
- scalability
- efficiency
- asymptotic analysis
These concepts allow programmers to evaluate solutions not only in terms of correctness, but also in terms of feasibility.
This introduces an important element of discernment into software development. Two programs may produce identical outputs while differing dramatically in computational cost. In such situations, the programmer must evaluate not merely whether a solution works, but whether the solution remains appropriate under changing conditions and constraints.
At the same time, algorithmic complexity cannot be reduced to a simplistic pursuit of optimization. Nuance is required because optimization itself introduces tradeoffs. Highly optimized code may become less readable, more difficult to maintain, or harder for teams to modify collaboratively. In some contexts, a simpler and less efficient solution may be preferable because it improves reliability, maintainability, or development speed.
Consequently, understanding algorithmic complexity involves more than memorizing Big O notation. It requires developing the intellectual capacity to evaluate competing priorities within a particular context.
Systemic Complexity
Where algorithmic complexity focuses on the cost of computation, systemic complexity emerges from interactions between components within a larger system.
Modern software systems rarely exist as isolated programs. Instead, they typically involve combinations of:
- databases
- APIs
- authentication systems
- front-end interfaces
- cloud infrastructure
- background services
- external dependencies
As systems become increasingly interconnected, their behaviour often becomes more difficult to predict. Problems may arise not because an individual component is malfunctioning, but because assumptions between components are inconsistent or incompatible.
For example, a database schema may function correctly in isolation, an API endpoint may return valid data, and a user interface may appear operational. Nevertheless, the system as a whole may still fail because the interactions between these components introduce unforeseen behaviours.
Systemic complexity therefore shifts the programmer’s attention away from isolated implementation details toward relationships, interfaces, and architecture. Questions such as the following become increasingly important:
- How do components exchange information
- What assumptions exist between systems
- How are errors propagated
- What happens when one subsystem fails
- How does change in one area affect the rest of the system
- modular but difficult to understand
- secure but inconvenient
- flexible but unstable
- highly efficient but resistant to future modification
- prototyping
- user testing
- evaluation cycles
- feedback collection
- revision
- which requirements are essential
- which tradeoffs are acceptable
- which assumptions about user behaviour may be inaccurate
- algorithmic complexity in search and data processing
- systemic complexity in coordinating databases, APIs, and interfaces
- social complexity in addressing the needs of users, administrators, and organizations
This form of complexity requires considerable nuance because large systems frequently involve competing design priorities. A system may be:
No design decision exists independently of consequence. Discernment therefore becomes essential in evaluating which compromises are acceptable within a given environment.
For this reason, experienced software engineers often devote substantial time to system design before implementation begins. In many cases, the long-term success of a project depends less on individual algorithms and more on the quality of the system’s structure and interfaces.
Social Complexity
While algorithmic and systemic complexity are primarily technical in nature, social complexity emerges from the interaction between software systems and human beings.
In practice, many software failures occur not because the underlying computation is incorrect, but because the system does not adequately address human needs, expectations, or behaviour. A technically sophisticated system may still prove ineffective if users find it confusing, frustrating, or irrelevant to their actual problems.
Social complexity arises because human requirements are rarely fixed or fully articulated at the beginning of a project. Users often struggle to describe precisely what they need. In many situations, important requirements only become visible after individuals begin interacting with prototypes or early versions of a system.
As a result, software development frequently involves iterative processes such as:
The design cycle exists largely because human needs evolve through interaction.
This introduces another layer of nuance into programming. Developers must learn to distinguish between stated problems and actual problems. Users may request features that do not address the underlying issue they are experiencing. Different stakeholders may prioritize conflicting goals. Systems that maximize efficiency may reduce accessibility, while systems designed for convenience may introduce security risks.
Discernment within this context involves interpreting ambiguity rather than eliminating it. The programmer must continuously evaluate:
Consequently, solving complex problems through programming requires more than technical proficiency alone. It also requires the ability to reason carefully about human systems, organizational constraints, communication, and adaptation.
Intersections Between Forms of Complexity
In authentic software engineering contexts, these forms of complexity rarely occur independently. Large-scale systems typically involve all three simultaneously.
A web application, for example, may involve:
One of the central educational goals of this course is therefore the development of intellectual flexibility. Students must learn to recognize which forms of complexity are most significant within a particular situation and to adjust their design decisions accordingly.
This process requires increasing levels of nuance and discernment. Not every inefficiency constitutes a meaningful problem. Not every abstraction improves a system. Not every user request should be implemented exactly as stated. Effective problem solving in computer science depends heavily upon the ability to evaluate context, constraints, consequences, and competing priorities simultaneously.
For this reason, solving complex problems through programming is best understood not simply as an exercise in producing functional code, but as a disciplined process of analysis, interpretation, design, and evaluation within environments characterized by multiple interacting forms of complexity.