Psychological Effects of Software Engineering Tenets

Many organizations define software engineering tenets (aka core values or principals) that serve as

Organizations often attempt to define software engineering tenets (aka core values or principles) that serve as a static mental and behavioral model to guild employees. Tenets are a powerful tool capable of inspiring people toward a common goal. However, poorly designed tenets can just as easily unite a community on a myopic quest toward inefficiency.

Tenets are essentially a simplified model of a problem space that reduces the cognitive load associated with decision making. The intent is to provide a shorter path to a good decision. These models often become a deeply ingrained component of engineers' psyches which are subject to the human proclivity of self-defense. Unfortunately, models that are overly-simplistic or inaccurate provide a shorter path to a bad decision that may be defended with disregard toward reason. This is why it's important to avoid tenets that promote silver bullet thinking or preclude reasoning from the engineering process.

Humans’ Affinity Toward Their World View

Humans have a deeply ingrained psychological need to categorize and simplify the world around them. Notice the deliberate use of the word need; it's much more than a mere idiosyncratic proclivity. Reducing complex phenomena to comprehensible models that can be conjured up by a single word or phrase makes the world tractable. Conflating new experiences with known phenomena provides the ability to quickly predict the behavior of the environment and the objects therein. The inevitable consequence is that models become part of the psyche: a deeply ingrained component of one’s world view. The whole of cognition rests upon this ability. This is the thesis of Douglas Hofstadter's seminal paper Analogy as the Core of Cognition. It would be easy to make a case that the many of humanity’s prodigious accomplishments are a consequence of this need.

Unfortunately, the same case could be made for many of humanity’s most heinous atrocities. Often, the simplified models humans create to predict the behavior of their environment are incomplete or inaccurate. These models are often expanded well beyond what is appropriate (see the concept of illusory correlation as defined by Stanvoich). To make matters worse, humans pugnaciously defend their world view as a self-defense mechanism (refer to Jonas Kaplan's body of work for a more comprehensive treatment of the theory). Self-defense operates at a baser level with indifference toward reason. This is essentially what Max Planck was referring to when he said, "An important scientific innovation rarely makes its way by gradually winning over and converting its opponents: What does happen is that the opponents gradually die out”. The Oatmeal illustrates this concept beautifully with his "You're not going to believe what I'm about to tell you" comic (

Human's ability to overcome this self-defense mechanism is a debate that reaches back to the 1700s with Kant's subject-object distinction. The argument comprises the very essence of the modern/post-modern philosophical dichotomy. The conclusions that have evolved out of these two lines of thoughts are truly astounding. Drawing any sort of conclusions about objective reality is well beyond the scope of this humble article. That is best left to the purview of philosophers. However, for the purpose at hand, there are a few salient points that can be accepted as fact:

  • Humans need models to make the world tractable
  • Models can be incomplete or inaccurate
  • Models become part of human's world view
  • Humans default to a bellicose attitude when protecting their world view

To relate these concepts to defining software design tenets, consider the interactions between software professionals and projects as a microcosm of the interactions that humans have with the world.

The Software Engineering Microcosm

Modern software professionals find themselves submerged in ambiguity. An epiphenomenon of the industry's relative adolescence is that the marketplace of ideas hasn't had enough time to standardize on best practices. There are seemingly countless competing concepts battling it out as you read this. Unless you can predict the future, there is no way to tell which ones will emerge victorious. Furthermore, the industry is severely lacking in meaningful empirical studies so it's hard to even define what "victory" means. Coupling all of this with ever-shrinking timelines, ever-evolving external security threats, ever-expanding feature requirements, and relentless pressure to generate revenue and things quickly appear unmanageable to the ablest of people. The plight is analogous to the struggle of an underprivileged juvenile trying to build meaningful models of the world without proper guidance. Surely not an impossible task, but success (depending on how you define it) is statistically improbable.

Make no mistake about it, the software industry needs simplified models in order to impose order on the chaos. The problem is, the industry has evolved a particularly pernicious tendency toward overly simplistic models. Typically, the models look something like this: Company X is an industry leader, they use Technology/Pattern Y, therefore Pattern Y is the solution to software productivity. Unfortunately, software professionals are still questing for the proverbial "silver bullet" which Fred Brooks denies the existence of at all.

Examples of fashionable mental models following the pattern above are virtually endless. Those who were programming in the early nineties (as was this author) undoubtedly remember the promised panacea of CASE tools. Next came n-tier architecture with its unfettered flexibility. Many espoused Ruby and other dynamically typed languages as the one true way because they lowered the barriers to entry. Conversely, proponents of languages such as Haskell proselytized expressive types systems as the end of the software crisis. There is undoubtedly much missing from the list. The latest silver bullet fervor is channeled toward micro-services and twelve-factor apps.

Without a doubt, there is someone reading this who is feeling dissonance because their world view has just been challenged. The author sympathizes with said reader because he has been in his/her shoes. As a young engineer, the author was challenged by a senior engineer on the efficacy of n-tier architecture. The immediate response was, "You are simply afraid of change and new paradigms. This is the RIGHT way to build software". It's amazing how time and experience changes one's perspective.

To summarize the key points:

  • There is much ambiguity around best practices in modern software engineering
  • Software professionals often adopt mental models that equate to silver bullet thinking; and this is irresponsible
  • When mental models become part of a software professional's world view they become defensive of them; and this reflects personal bias which must be acknowledged and questioned if one is to every grow because nothing is static

Don't make the mistake of interpreting this piece as an attack on any particular architectural pattern. Every model mentioned has undeniable merit. The underlying fallacy stems from silver bullet thinking that espouses the RIGHT (and suggestively ONLY) way to build software.

The RIGHT Way to Build Software

A reasonable person could challenge the thesis of this article by enumerating the benefits of their said model and asserting that it may not be a silver bullet, but it is certainly a superior approach - so it is, therefore, the right way to build software. The glaring fallacy in that reasoning is that it attaches value propositions outside of a context. This is what Micheal Glinter would classify as Level One Thinking.

Robert Glass has written extensively on this topic in several books and articles. In his book Software Creativity 2.0 he states the following concerning silver bullet thinking: "we have those who proclaim each new idea that comes along as the solution to software productivity. ... These people, I would assert, are the level one thinkers. Some perceive them as strong because they see a solution clearly and move swiftly toward it. Others see them as simplistic, for they ignore the complexity in the problem and seem unable to accept the ambiguity."

Unfortunately, the second law of thermodynamics applies to software: there is no such thing as a free lunch. Architectural patterns are nothing more than a series of trade-offs. Two pertinent points follow from this assertion. The first is that any person who only enumerates advantages without also explaining the cost of said benefits, simply DOES NOT have a deep understanding of the architectural pattern or is selling something. The second is that it is nonsensical to weigh trade-offs outside of a context. Each project has unique requirements that give meaning to trade-offs. No trade-off is always best, but it may be best for the use case in question.

Evaluating trade-offs outside of a context is not only nonsensical, it's also destructive and the pernicious effects are virtually never-ending. It diverts the focus from the software's true purpose of providing value to end users to arbitrary patterns. Concisely stated, it prioritizes the process above the product. This equates to wasted efforts and resources. Furthermore, once engineers' adopt an overly-simplified mental model, they are psychologically incentivized to pursue and perpetuate it.

To summarize:

  • There is no “right” way to build software
  • Every approach is a series of contextual trade-offs
  • It's nonsensical to evaluate trade-offs outside of a context
  • Evaluating trade-offs outside a context prioritizes the process over the product

In the event that an organization must communicate an affinity toward a trade-off, these should be considered non-functional requirements and not tenets.

Non-Functional Requirements are NOT Tenets

It makes sense that some organization would want to communicate an affinity toward a side of a trade-off. For instance, financial institutions may naturally prioritize security over availability. Specifying these preferences is particularly important because they typically never come to bear from iterative development. However, it makes more sense to express these as non-functional requirements instead of tenets. This may seem like nothing more than linguistic gymnastics; however, the psychological effect is very real. Consider the meaning of the two words:

Tenet: a principle, belief, or doctrine generally held to be true (Merriam-Webster)

Non-Functional Requirement: a requirement that specifies criteria that can be used to judge the operation of a system, rather than specific behaviors (Wikipedia)

Specifying a trade-off as a tenet removes reason from the engineering process and has the unintended consequence of creating an overly-simplistic model of the software engineering world. Conversely, specifying a non-functional requirement biases the engineer toward an engineering mindset.

There is one key takeaway here:

  • Specifying a non-functional requirement as a tenet has the unintended psychological effect of removing reason from the engineering process

Hopefully, this section made the case that it's undesirable to specify non-functional requirements as tenets and the previous section established that silver bullet thinking is destructive. The question remains, what exactly is a good software engineering tenet?

Anatomy of Healthy Tenets

A healthy tenet is ambiguous enough to facilitate the engineering process yet precise enough to focus effort. One of the best examples of a healthy tenet is Amazon's Customer Obsession principal:

Leaders start with the customer and work backwards. They work vigorously to earn and keep customer trust. Although leaders pay attention to competitors, they obsess over customers.

The principal does not bias engineers toward any particular technology or pattern. Likewise, it doesn't create a cognitive bias toward an approach. However, it does bias thinking toward the deep compilation of use cases. In short, it keeps the focus on the product, not the process.

Another perfect example of a good tenet is Amazon's Invent and Simplify principal:

Leaders expect and require innovation and invention from their teams and always find ways to simplify. They are externally aware, look for new ideas from everywhere, and are not limited by “not invented here”. Because we do new things, we accept that we may be misunderstood for long periods of time.

The attribute that makes this tenet great is that it encourages the relentless pursuit of the perfect balance of trade-offs. It creates a bias toward simplicity. It does not remove reasoning from the process; rather it forces proponents to think critically. These are the kind of tenets that create harmony.

As a small aside, make sure to avoid the trap of copying the tenets of industry leaders. The big technology companies didn't get where they are by forcing solutions into their contexts. They aggressively engineered solutions to meet their contextual needs. It only makes sense to be aware of the practices of successful enterprises; however, accept that they operate in a different context from yours. Create solutions for YOUR context.

To summarize, good tenets:

  • Do not blindly prescribe any particular technology, approach, or pattern
  • Require proponents to think critically at all times, this includes questioning self to guard against one’s own bias
  • Create a bias toward simplicity but not over-simplicity
  • Focus on products, not process

Wrapping it Up

Human cognition is a fascinating subject with profound implications for the engineering process. Regardless of an individual's reasoning ability, they are limited by their cognitive architecture and subject to the perils of defending their contextual world view. Accepting software engineering tenets contributes to an engineer's world view. This is why it is important to create tenets without unintended negative biases (to the extent that's possible) and to revisit and revalidate them over time.

Tenets that perpetuate "silver bullet" thinking or establish non-functional requirements are particularly noxious. They tend to exclude critical thinking from the engineering process. Additionally, they place emphasis on the process rather than the product. Conversely, healthy tenets refrain from prescribing any particular technology, approach, or pattern. They force proponents to think critically and choose the perfect balance of trade-offs. With a bias toward simplicity, the focus remains firmly on the product.

Zero to DevOps in Under an Hour with Kubernetes (CodeMash Conference Talk)

Zero to DevOps in Under an Hour with KubernetesBelow is a video of a talk I gave a CodeMash in Febru

Zero to DevOps in Under an Hour with Kubernetes

Below is a video of a talk I gave a CodeMash in February 2018.




Gödel, Artificial Intelligence, and Confusion

Sentient software is the hot topic as of late. Speculative news about Artificial Intelligence (AI) s

Sentient software is the hot topic as of late. Speculative news about Artificial Intelligence (AI) systems such as Watson, Alexa, and even autonomous vehicles are dominating social media. It’s feasible that this impression is nothing more than Baader-Meinhof phenomenon (AKA frequency illusion). However, it seems that the populace has genuine interest in AI. Questions abound. Are there limits? Is it possible to create a factitious soul? Gödel’s incompleteness theorem is at the core of these questions; however, the conclusions are cryptic and often misunderstood.

Gödel’s incompleteness theorem is frequently adduced as proof of antithetical concepts. For instance, Roger Penrose’s book Shadows of the Mind claims that the theorem disproves the possibility of sentient machines (Penrose, 1994, p. 65). Douglas Hofstadter asserts the opposite in his book, I Am Strange Loop (Hofstadter, 2007). This article aims to provide a cursory view of the theorem in laymen’s terms and elucidate its practical implications on AI.


Gödel’s Incompleteness Theorem is best understood within its historical context. This section covers requite concepts and notable events to provide the reader with adequate background knowledge. This is not meant to be comprehensive coverage of the material: rather it is stripped down to essentials.

The Challenge

The mathematics community was never filled with more hope than at the turn of the twentieth century. On August 8th, 1900, David Hilbert gave his seminal address at the Second International Congress of Mathematics in which he declared, “in mathematics there is no ignorabimus” (Petzold, 2008, p. 40). Ignorabimus is a Latin word meaning “we shall not know”. Hilbert believed that, unlike some other branches of science, all things mathematical were knowable. Furthermore, he framed a plan to actualize a mathematical panacea.

In this address, Hilbert outlined ten open problems and challenged the mathematics community to solve them (this was a subset of twenty-three problems published by Hilbert). The problem of relevance for this article is the second which is entitled, The Computability of Arithmetical Axioms. Hilbert’s second problem called for the axiomatization of real numbers “to prove that there are no contradictory, this is, that a finite number of logical steps based upon them can never lead to contradictory results” (Petzold, 2008, p. 41). More concisely, Hilbert wished to axiomatize number theory.

The following sections delve into axiomatization. However, a pertinent idea here is the phrase “finite number of logical steps”. In modern nomenclature, this is known as algorithmic. Hilbert, along with his contemporaries, believed that every mathematical problem was solvable via an algorithmic process. (Petzold, 2008) This is a key concept that will be revisited after exploring axiomatization.


Stated concisely, axiomatization is a means of deriving a system’s theorems by logical inferences based on a set of axioms. Axioms are unprovable rules that are self-evidently true. The most well-known axiomatized system is Euclidean geometry; therefore, it serves as an archetype for understanding axiomatic systems. The whole of Euclidean geometry is based on five axioms.

  1. A straight-line segment can be drawn joining any two points.
  2. Any straight-line segment can be extended indefinitely in a straight line.
  3. Given any straight-line segment, a circle can be drawn having the segment as radius and one endpoint as center.
  4. All right angles are congruent.
  5. If two lines are drawn which intersect a third in such a way that the sum of the inner angles on one side is less than two right angles, then the two lines inevitably must intersect each other on that side if extended far enough.

(Wolfram Research, Inc., 2017)

As a small aside, the fifth axiom is also known as the parallel postulate. This has the been the subject of mathematical quandary for centuries. It is highly recommended that the enthusiastic reader perform additional research on the subject.

These five axioms form the foundation of geometry. Pythagorean theorem, Pons Asinorum, Congruence of triangles, Thales' theorem, and countless others are derived via logical inferences based on the assumption that these self-evidentiary axioms are true. Axioms provide a solid foundation for a system, much like the cornerstone of a building.

Another key concept introduced in the previous paragraph is logical inferences. It’s not enough to have a firm foundation of axioms. Theorems derived from the axioms must be likewise sound and logical inference offers a guarantee of said soundness.

Logical Inference

The process of connecting axioms to theorems cannot rely on intuition in any way. This is to say that they are definitive rules and constructs in which logical inference can be validated. This is important because the legitimacy of axioms is irrelevant if conclusions drawn from them are not completely consistent. A strong, stable, and trusted system must be composed of theorems that use valid logical inferences stemming from axioms.

It is beyond the scope of this blog post to give even a cursory explanation of logical systems of inference. However, it’s important for the reader to understand that formal logic has stringent rules and notations much like any mathematical system. Logic statements are written and manipulated like any other mathematical formulas. This allows for the creation of proofs that cement the validity from the bottom up.

Each theorem is analogous to a brick in a house. Because the theorem sits firmly on either an axiom or another theorem planted on an axiom, it’s validity is confirmed. This is commonly known as infinite regress. All the theorems taken together form a strong and stable system capable of being trusted. Formalism expands on the concept.


Recall the Computability of Arithmetical Axioms problem outlined in The Challenge section. Hilbert envisioned Formalism as the solution to this problem. Formalism, as conceived by Hilbert, is a “system comprised of definitions, axioms, and rules for constructing theorems from the axioms” (Petzold, 2008, p. 45). It is often described as a sort of metamathematics. Hilbert envisioned a formal logic language where axioms are represented as strings and theorems are derived by an algorithmic process. These concepts were introduced in the previous two chapters. A new concept to this section is the qualities that such a system must possess.

For a system, such as formalism, to truly axiomatize the whole of arithmetic, it must have four qualities which are outlined below.

  • Independence – There are no superfluous axioms.
  • Decidability – A algorithmic process for deriving the validity of formulas.
  • Consistency – It is NOT possible to derive two theorems that contradict one another.
  • Completeness – Ability to derive ALL true formulas from the axioms.

(Petzold, 2008, p. 46)

As a small aside, there is a fair bit of legerdemain happening here. The concepts of truth, formulas, theorems, and proof are purposely glossed over to avoid minutia. Curious readers are encouraged to investigate further.

The two qualities that are particularly cogent to Gödel’s incompleteness theorem are consistency and completeness. Luckily, they are both self-explanatory. A system that is both complete and consistent will yield all possible true formulas, none of which are contradictory.


The truth is that axiomatization is a fastidious process that can seem maddingly pedantic. One may be forced to question the very premise that it is a good thing. One can further postulate that simple human intuition is sufficient. However, recall the concept of infinite regress called out in the last paragraph of the Logical Inference section. New theorems are built upon existing theorems. Without stringent formal logic rules, systems become a “house of cards”. Mistakes found in foundational theorems can bring the entire system crashing down.

An archetypal example is Cantor’s set theory. The details of the theory are largely irrelevant to this line of inquiry, but the curious reader should refer to this set of blog posts for more information. In short, set theory took the mathematical world by storm. Countless mathematicians augmented it by building new abstractions on top of it. Bertrand Russel discovered a fatal flaw known as Russel’s Paradox which brought the system down like a proverbial “house of cards”. Formalism is meant to avoid similar debacles.

Principia Mathematica

The Principia Mathematica is an infamous three-volume treatise by Alfred North Whitehead and Bertrand Russell published in 1910, 1912, and 1913. It is a truly herculean attempt to formalize the whole of arithmetic. The work is dense and inaccessible to even most mathematicians (Nagel & Newman, 2001). The system set forth sets the stage for Gödel’s incompleteness theorem.

Incompleteness Theorem

In 1931, Kurt Gödel published a seminal, albeit recondite, paper entitled On Formally Undecidable Propositions of Principia Mathematica and Related Systems. The paper dismayed the whole of the mathematical community despite its esoteric content. It not only trampled the validity of Principia Mathematica, it proved that such a system isn’t achievable by any means. The implication being that Hilbert’s second problem, The Computability of Arithmetical Axioms, will never have a satisfactory solution.

In short, Gödel proved that any system complex enough to encompass simple arithmetic cannot be both complete and consistent as defined in the Formalism section. Through a clever method of converting logical expressions to numbers, the proof showed that any such system will enable the creation of a self-referential statement in the form of “this statement is false”.

The previous paragraph is a blatant over-simplification of Gödel’s incompleteness theorem. The intimate details of the proof are well beyond the scope of this humble article. As mentioned so many times throughout this work, the reader is encouraged to continue research independently. On a positive note, the arcane details are not requisite for comprehension of the implications.


In short, the implications of Gödel’s Incompleteness Theorem are nothing more than that an axiomatic system of logic cannot be both complete and consistent. Expanding on that, it is not possible to derive an algorithm that will generate all possible proofs of a formalized system. One can then infer that it is not possible to write a computer program to generate said proofs.

There have been countless extrapolations based on the implications stated above. For instance, a commonly adduced argument is that there are more truths in the universe than there are proofs. Likewise, there are some things that are obviously true that cannot be formally proven. While these are both true, be careful not to fall into the enticing trap of applying the rule to anything outside of axiomatic systems of logic.

Why the Confusion?

Although it’s a rather unsatisfying observation, the reality is that Gödel’s proofs are onerous to all but accomplished logicians. Despite this, the implications are far reaching. This situation creates a particularly fertile breeding ground for misconceptions. Many venerated experts within other disciplines attempt to apply the theorem by fallacious means.

A cursory Google search for “Gödel’s incompleteness theorem and God” will yield seemingly boundless results with varied interpretations. The fact of the matter is, the theorem strictly applies to formal axiomatic systems of logic. It does not apply to religious texts. Likewise, it has no implications on the validity of the afterlife or mystical intuition. (Tieszen, 2017, p. Kindle Loc. 1173)

As an example, Gödel’s ontological argument is often cited by theists because it formally proves the existence of God. Given the description, it is easy to see how someone ignorant of formal logical proofs could draw fallacious conclusions. As stated previously, Gödel’s proofs apply exclusively to formal axiomatic systems of logic. The concept of God is far from this. Gödel himself said that “it was undertaken as a purely logical investigation, to demonstrate that such a proof could be carried out on the basis of accepted principals of formal logic” (Tieszen, 2017, p. Kindle Loc. 2158). He also hesitated to publish “for fear that a belief in God might be ascribed to him” (Tieszen, 2017, p. Kindle Loc. 2158).

The cogent point is that it is easy to misinterpret the significance of Gödel’s work. It is difficult for anyone lacking a strong background in mathematical logic to draw valid conclusions based on the incompleteness theorem. Gödel’s work is best confined to scientific contexts.

Implications for Artificial Intelligence

The thesis of this work is to define the implications of Gödel’s incompleteness theorem on AI. Unfortunately, a surfeit of background concepts is requisite to comprehension and the author humbly apologizes for the necessary discomfort. Possibly more disappointing is that the verdict is not as definitive as one may suppose as this section explains.

One thing is definite, it is not possible to use a computer to automatically derive proofs from an axiomatic system. Hilbert’s dream of automated formalization is inert. On the bright side, if it were many mathematicians would be out of work. Some claim, as does Roger Penrose, that this necessarily precludes any possibility of AI within the current computational model. Consider this, a human can necessarily comprehend some truths that a machine cannot. The insinuation is that humans are endowed with creativity that is not obtainable by a machine. Mr. Penrose postulates that this is a quantum effect that is beyond our current understanding. (Penrose, 1994)

Douglas Hofstadter passionately refutes Roger Penrose’s claims. He believes that the said limits stem from a fundamental misunderstanding of how the brain works and presents a compelling model of consciousness in his book, I Am Strange Loop (Hofstadter, 2007). Theorem proving is by no means the only way to make a machine “think”. “The human mind is fundamentally not a logic engine but an analogy engine, a learning engine, a guessing engine, and esthetics-driven engine, a self-correcting engine” (Nagel & Newman, 2001, p. Kindle Loc. 146). From this frame of reference, Gödel’s incompleteness theorem doesn’t apply to AI.

Penrose and Hofstadter sit among varied experts with similar opinions. With the considerable amount of resources funneled into AI projects, the final verdict will be decided in due course of time. Not that this should sway the reader in any way, but the author tends to side with Mr. Hofstadter. The reader is encouraged to do their own research and form their own opinions.


Gödel’s incompleteness theorem is inextricably associated with philosophy, religion, and the viability of Artificial Intelligence (AI). However, Gödel’s work is in a recondite field and its applicability beyond axiomatic systems of logic is perplexing and often misapplied. In the final analysis, the theorem’s only definitive assertion is that it is not possible for an axiomatic system of logic to be both consistent and complete. Many experts make conflicting ancillary claims and it’s difficult to draw any absolute conclusions.

This article presents a simplistic high-level view of Gödel’s incompleteness theorem aimed at the novice with limited exposure. It is highly recommended that readers use this as a starting point for much deeper exploration. The books listed in the bibliography are all excellent references for further research.


Hofstadter, D. (2007). I Am A Strange Loop. Retrieved 8 27, 2017

Nagel, E., & Newman, J. R. (2001). Gödel's Proof: Edited and with a New Foreword by Douglas R. Hofstadter. (D. Hofstadter, Ed.) New York University Press, NY. Retrieved 8 27, 2017

Penrose, R. (1994). Shadows of the Mind. Oxford University Press p. 413. Retrieved 8 27, 2017

Petzold, C. (2008). The Annotated Turing. Indianapolis: Wiley Publishing, Inc.

Tieszen, R. (2017). Simply Gödel. New York: Simply Charly.

Wolfram Research, Inc. (2017, October 30). Euclid's Postulates. Retrieved from Wolfram Math World:


The goal of this article is to provide laymen with a conceptual understanding of diagonalization. Th

The goal of this article is to provide laymen with a conceptual understanding of diagonalization. Those interested in a deep dive full of mathematical jargon will be sorely disappointed. However, this piece is the perfect resource for a general understanding of the topic devoid of the more arcane details. Unlike the majority of my writing, this is not directly applicable to the daily responsibilities of software professionals. It is purely an endeavor to satisfy intellectual curiosity.


The impetus for this writing comes from a colleague who contacted me after reading my blog series on Set Theory (Set Theory Defined, Set Operations, When Sets Collide). The posts made pithy mention of Cantor’s diagonalization proof with implications on infinite cardinality. My friend’s search for a concise explanation proved to be unfruitful. The conversation naturally progressed toward Alan Turing’s seminal paper: On Computable Numbers, which also employs a diagonalization proof. Cantor and Turing both played a major part in shaping computer science. Therefore, although it is not likely that the majority of software professionals will ever employ diagonalization, it’s a crucial part of computing history.

What Are We Trying to Prove?

Diagonalization is a mathematical proof demonstrating that there are certain numbers that cannot be enumerated. Stated differently, there are numbers that cannot be listed sequentially. Consider all the numbers on the number line as shown in Figure One – Number Line.


First consider the set of positive whole numbers including zero. These are known as natural or counting numbers and are denoted as `\mathbb{N}`. Most kindergarten curriculum teaches how to enumerate this set: starting with zero add one to the current number to get the next number ad infinitum.

Adding negative numbers to `\mathbb{N}` produces the set of integers denoted by `\mathbb{Z}`. Again, this set is also easy to enumerate by simply listing it as follows: `0, 1, -1, 2, -2, 3, -3, …`.

Now consider expanding on `\mathbb{Z}` by adding fractions to create the set of rational number denoted as `\mathbb{Q}`. The term rational signifies that a number can be expressed as a ratio such as `1/2` or `23/345`. These numbers fit between the whole number on the number line and there is an infinite amount of fractional numbers between each set of natural numbers. That is to say, regardless of the location of two rationals on the number line, it’s always possible to find another number between them. With some ingenuity, these numbers can also be enumerated in several different ways. Enumerating rational numbers, while fascinating, is beyond the scope of this post. The reader is encouraged to either just accept my word as fact or do research. Here is a good place to start.

Although it seems as if we’ve run out room on the number line, that isn’t actually the fact. There is another class of number that has been baffling mathematicians throughout the ages: irrational. It’s a bit perplexing, but irrationals fit between rationals on the number line (no matter how many times I think about that, it amazes me). Grade school curriculum typically introduces the concept with renowned numbers such as `\pi` or `e`. These are numbers that cannot be expressed as a ratio. The decimal representation consists of an infinite series of digits with no repeating pattern. Any calculations involving irrationals are approximations because it’s impossible to express them in a finite context. Adding these to `\mathbb{Q}` produces the set of real numbers denoted as `\mathbb{R}`. Irrational numbers are the target of our inquisition.

As a matter of note, the set of irrational numbers can be further divided into the sets of algebraic and transcendental numbers. Algebraic numbers can in fact be enumerated. However, this is a bit of minutia that isn’t really necessary for understanding diagonalization. Once again, the curious reader is encouraged to rely on Google for further inquiry.

The question is, how is it possible to prove that irrational numbers are not enumerable. With an understanding of the problem, we can turn our attention to the solution which is diagonalization.

Reductio Ad Absurdum

Diagonalization is a type of proof known as reductio ad absurdum which is Latin for reduction to absurdity. It is common amongst mathematicians and philosophers alike. The premise is to first assume a proposition is true and then disprove it via deductive reasoning thus reducing it to an absurd conclusion.

One popular example of a reductio ad absurdum proof is that there is no smallest fractional number. Assume there is such a number: it can be divided by two to create a smaller number. Therefore, the original assumption is absurd. Another illustration is an alibi. First assume the suspect committed the crime. If the accused is known to be at a different location when the crime took place, it’s absurd to assume that they were also at the scene of the crime.


Having addressed all the introductory trivialities, it’s time to get to the point. The diagonalization proof is as follows. First assume that it is possible to enumerate all irrational numbers. If this is true, it should be impossible to devise a number that is not included in this list. Examine Figure Two – Diagonalization and stretch the mind to imagine that this is in fact the list of all irrational numbers: the list is infinitely long and each number expands on endlessly. Next, draw a diagonal line down the center of the list and write down the resulting infinite number. In this case, the number is `0.13579135…`. Next add 1 to each digit expect in the case of nine which becomes a zero. This results is the number `0.24680246…`. Is this number contained in the list? It’s obviously not the first number because the first digit does not match. The same holds true for the second number because the second digit has to be different. Continue this line of logic for every number and it’s obvious that the devised number is not in the list. The reader should take a few minutes to let that sink in.


Keep in mind, this is purely a thought experiment. Obviously, Figure Two – Diagonalization is not an infinite list and each number is not truly irrational. It’s impossible to construct such a list in a finite context. However, the line of logic holds true.

It is common to wonder why diagonalization does not apply to `\mathbb{Q}`. The concise answer is that those numbers have finite digits and irrationals do not.


Accepting that the diagonalization proof is valid, it has some profound implications. At first glance, it’s difficult to understand how the fact that it’s impossible to enumerate irrational numbers has bearing on the world in any way. However, many people have derived some amazing conclusions. Cantor showed that there are in fact multiple infinities. Turing used diagonalization to prove the limits of computability. It’s even been employed by philosophers to prove that there are an insufficient number of proofs to prove all the truths in the universe. More concisely, some truths are unproveable. The implications lead down an exceedingly dark and deep rabbit hole.


Diagonalization is a reductio ad absurdum proof that demonstrates the impossibility of enumerating irrational numbers. It is relatively easy for non-mathematicians to understand. While only tangentially related to software engineering, it’s a fascinating concept that sheds light on the foundations of computing and indeed the world.

As always, thank you for taking the time to read this article. Please feel free to contact me with any questions or concerns.

Just Enough Set Theory – When Sets Collide (Part 3 of 3)

Welcome to the final installment of this three-part series on set theory. The first piece, Set Theory Defined, detailed requisite foundational knowledge. The second article, Set Operations, outlined some beneficial set algorithms. This post develops the concepts laid out in the first two; therefore, it is highly recommended that readers begin there.

Individual sets have many useful properties; however, preforming operations on multiple sets provides even greater utility. This piece outlines four such operations. Each operation provides a concise means for addressing common programming problems that virtually all software professionals encounter. There is a brief description of each from a mathematical perspective followed by JavaScript (ES6) code excerpts demonstrating how to apply theory to real world scenarios.

NOTE: All code samples are written in ES6 and are therefore not likely to execute directly in a browser. The best option is to use Node or transpile the excerpts using either Babel or TypeScript. The working code is available on GitHub along with execution instructions.


The union of two sets is a set containing the distinct elements from both sets. `\cup` is the mathematical symbol for a union and the union of sets `A` and `B` is denoted as `A \cup B`. An expanded way of representing the union relationship is `\{x| x \in A \vee x \in B\}`, which means every element contained in `A` OR (`\vee`) `B`. Figure One – Union depicts two sets with three elements each. The union is a set with five elements because one item, three, is shared and union returns distinct values. The Venn diagram shows the relationship graphically.


Generating the union of two sets is quite easy in ES6 as the code below illustrates.

const A = new Set([1, 2, 3]);
const B = new Set([3, 4, 5]);
const union = new Set([...A, ...B]);
// union = [1,2,3,4,5];

The astute reader will notice that there’s some legerdemain afoot. The code above uses the ES6 Set data structure instead of standard JavaScript arrays. Set holds only unique elements by ignoring add operations for new values that match existing ones. The algorithm is as easy as concatenating the two sets without the concern of distinct elements. If the code was using standard arrays, there would have to be logic to remove duplicated items. Luckily, converting between sets and arrays is virtually effortless.

const setDataStructure = new Set([1, 2, 3]);
const arrayDataStrcture = Array.from(setDataStructure);

The problem with the code above is that it’s a rare requirement to union sets containing primitive values. Software engineering is seldom that straightforward. A more realistic scenario is calculating the union between two sets of complex objects where equality becomes problematic. Unlike primitive variables, objects with identical values are not equal because they compare by reference. This abrogates the Set trick from earlier. Suppose the requirement is to compute all bug reports currently in process across two teams and it’s possible that both teams are working on the same bugs simultaneously. The code below demonstrates a solution by first concatenating the two sets and then removing duplicates using the filter method introduced in the last article. Notice the only equality check is via the Id. Obviously, this won’t work for every scenario and depending on the size of the sets and performance requirements it is possible to write generic deep equality methods (or use a library like underscore).

const teamABugs = [
    { id: 1, name: "Screen Explodes" },
    { id: 2, name: "Keyboard Burts into Flames" },
    { id: 3, name: "Submit button off by 1 pixel" }];
const teamBBugs = [
    { id: 5, name: "Randomly Dials Russian Hackers" },
    { id: 6, name: "Publishes CC info to the www" },
    { id: 3, name: "Submit button off by 1 pixel" }];

const union = [...teamABugs, ...teamBBugs]
    .filter((x, index, array) => array.findIndex(y => == == index);


The intersection of two sets is a set containing distinct shared elements. `A \cap B` is the mathematical representation of a union and the expanded notation is `\{x|x \in A \wedge x \in B \}`. Stated differently, the intersection of set `A` AND (`\wedge`) `B` is every element contained in `A` AND `B`. Figure Two – Intersection depicts the relationship showing the union of `A` and `B` to be a singleton set containing only the number three. Once again, the Venn diagram portrays the relationship.


Much like union, finding the intersection of two sets using the Set data structure and primitive types is easy. The code below shows how it’s a matter of using the filter method to check to see if an item is also stored in the other set.

const A = new Set([1, 2, 3]);
const B = new Set([3, 4, 5]);
const intersect = [...A].filter(x => B.has(x));
// intersect = [3];

The code above is a bit fanciful. Consider instead a role protected resource. Possessing any one of many roles allows users to access said resource. Users each have a set of associated roles. There are a few different ways to achieve this, but finding the intersection between the user’s roles and the resource’s required roles is the most manageable. See the code below.

const resourceRoles = [
    { id: 1, name: "Administrator" },
    { id: 2, name: "Super User" }];
const user =  { id: 314, name: "Edsger Dijkstra", roles: [
    { id: 1, name: "Administrator" }, 
    { id: 2, name: "User" }] }

const hasAccess = resourceRoles
    .filter(x => user.roles.find(y => == > 0;

All of the caveats about equality described in the Union section also apply here. It’s something programmers need to be cognizant of.


The difference of two sets is sometimes known as the relative complement; both nomenclatures are interchangeable. The concept is simple, the difference is a set made up of the items that are left over after removing the intersection of another set. Otherwise stated, all of the items in set `B` that do not exist in set `A`. Mathematically, this is represented as `\{x|x \in B \wedge x \notin A\}` or the shortened version which is `B\\A`. Figure Three – Difference shows the difference between `B` and `A` to be a set containing four and five. Just as above, there is a representative Venn diagram.


As an aside, there is also an absolute compliment which is somewhat similar; however, it is outside the scope of this article.

Finding the difference of sets is almost identical to finding the intersection as the code below demonstrates. The only variation is that the predicate passed to the filter method is negated.

const A = new Set([1, 2, 3]);
const B = new Set([3, 4, 5]);
const difference = [...B].filter(x => !A.has(x));
// difference = [4,5];

Again, a more realistic example is in order. Image that there is a set of actions that must be completed and a set of actions a user has completed. Finding the difference is an easy way to determine if all required actions are complete.

const requiredActions = [
    { id: 1, name: "Electronic Signing" },
    { id: 2, name: "Submission Form" },
    { id: 3, name: "Payment" }];
const userActions = [
    { id: 1, name: "Electronic Signing" },
    { id: 2, name: "Submission Form" }];

const complete = requiredActions
    .filter(x => !userActions.find(y => == === 0;
// complete = false

Cartesian Product

The Cartesian product of two sets is a set of ordered pairs that contain all possible combinations of elements in the two sets. The mathematical representation is `A \times B`. The expanded notation is `\{(a,b)|a \in A \wedge b \in B\}` which means an ordered pair consisting of every element in `A` AND (`\wedge`) every element in `B`. Figure Four – Cartesian Product demonstrates the concept. As a matter of importance, unlike standard products, the Cartesian product is not commutative. Stated mathematically, `A \times B \ne B \times A`. Switching the order of statement will change the order of the pairs.


The Cartesian product is useful for combinatorics problems. A common example is simulating a deck of cards. Instead of specifying all the cards explicitly in code, it’s easier to define the suits and values as two separate sets and then take the Cartesian product to get the entire deck. See the code below.

const suits = ['Diamond', 'Spade', 'Heart', 'Club'];
const values = ['Ace', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King'];

const cards = suits.reduce((acc, x) => [...acc, => [x, y])], []);
// Alternatively, it’s possible to return the ordered pair as an object instead of an array
// const cards = suits.reduce((acc, x) => [...acc, => { return { suit: x, value: y } })], []);

This code should be starting to look familiar because all the samples make heavy use of the map, reduce, and filter methods. Using ES6, these methods have great utility for mimicking mathematical set operations. Because the code above is similar to previous examples, it doesn’t require further explanation.

Why Stop at Two?

Up to this point, all the exhibited set operations employ two sets. However, this is for the sake of brevity. Each operations can act on as many sets as required. For instance, `A \cup B \cup C` is perfectly valid as is `A \times B \times C`. The enthused reader should solidify his/her learning by expanding each code sample to use additional sets.

Real World Applications

This series demonstrated how set theory is applied to data structures and demonstrated some novel uses for set operations in order create efficient algorithms. However, this is only a meager representation of all the many and varied applications for software engineering. Relational databases make heavy use of set theory for defining data structure and constructing data queries. In fact, SQL is essentially a set notation. There are several instances in language theory and design where strings are realized as sets and set operations are performed on them. Another prolific use is in computer graphics where points on a plane are treated as sets. The list of applications is considerable. It’s a body of knowledge that no software professional should forsake.


Thus concludes this three-part series on set theory. Hopefully, the reader has gained a high-level understanding as well as enough practical knowledge to apply the learning forthwith. The first article outlined the basics and introduced the concept of set mapping. Empty sets, cardinality, subsets, summation, and power sets were introduced in the second piece. Finally, this post presented operations involving more than one set including unions, intersections, differences, and Cartesian products. The method was to first introduce the ideas mathematically and then demonstrate how to apply them using ES6. These concepts should not be considered optional for software professionals because set theory is ubiquitous in computer science.

As always, thank you for reading and please feel free to contact me with questions. I’m also happy to create more in depth posts upon request.