Why You Should Consider Graphs For Your Next GraphQL Project

This article is a sponsored by Neo4j

The explosion of GraphQL over the past few years has introduced many front-end developers to the concepts of data modeling and storage, turning front-end developers into full-stack developers.

GraphQL provides developers working on a simple contract with a database, guaranteeing consistency and predictability of the data returned while also managing persistence and data fetching. The developer trusts the API to store and retrieve the data most efficiently.

But convenience comes at a cost. One day, your side project hits the front page of Hacker News, and a sudden influx of users grinds your database to a halt. Sometimes, the remedy is as simple as using the right underlying database for the loads.

In this article, I will look at the Graph behind GraphQL and demonstrate why Neo4j is the best fit for your next project.

The Graph In GraphQL

GraphQL itself is a database-agnostic query language. Many database companies and startups now offer libraries that convert a GraphQL query or mutation into a query language that works with the underlying data store, whether that be SQL for relational databases, Cypher for graph databases, or any number of proprietary query languages.

Graphs provide a natural way to represent data, where Nodes (or vertices) that represent entities or things are connected together by Relationships (or edges). Depending on the underlying data storage in your GraphQL library of choice, a certain amount of gymnastics may be involved. Suddenly, unnatural tables with strange names are created, or data is duplicated to improve query response times, introducing technical debt along the way.

This is where Neo4j comes in. Neo4j is a native Graph Database. Graph Databases are in a category all of their own, and for a good reason.

Graph databases treat the connections between data as first-class citizens, storing relationships so that highly connected datasets can be queried in real-time.

An Example: Movie Recommendations

Say we’re sick of scrolling through an endless list of thumbnails on our favorite streaming platform looking for something to watch. We decided to build a new website where users can register, provide movie ratings, and in return, receive movie recommendations based on users who have similar ratings.

In our GraphQL schema, we define types that represent movie information. Users provide movie ratings, each with a score between 1 and 5. Movies can have one or more actors and one or more directors. Movies are also tagged with one or more genres.

Note: Luckily, this Recommendations dataset already exists as a free Neo4j Sandbox. Neo4j Sandbox instances are free of charge, initially run for three days, and can be extended up to 10 days.

type User {
  userId: ID!
  name: string
  email: string
  ratings: [Rating]
}

type Rating {
  user: User!
  movie: Movie!
  rating: Int
  createdAt: Date
}
type Movie {
  movieId: ID!
  title: String
  released: Date
  actors: [Role]
  directors: [Person]
}

type Role {
  person: Person!
  movie: Movie!
  roles: [String]
}
type Person {
  personId: ID!
  name: String!
  born: Date!
  roles: [Role]
  directed: [Movie]
}

Let’s take a look at how this data will be stored in a relational database, a document store, and a graph and see where we might hit a problem when trying to generate recommendations.

In A Relational Database

Relational databases provide a structured method of data storage where data is organized into tables. Tables conform to strict rules known as a database schema, where each row contains a set number of columns, each with a set data type. Where a value may not exist, nullable columns can be used.

The underlying database schema provides a perfect base to map GraphQL Type Definitions. Each field within a type description will map one-to-one with a column. Those Type Definitions can be quickly translated into an SQL query (SQL stands for Structured Query Language) to insert or retrieve data.

A JOIN is constructed at read-time for nested types, joining two tables using foreign keys to find the corresponding records in a database. Here comes the first potential problem.

Let’s look at an Entity Relationship Diagram (ERD) that describes how the data may be stored in a relational database.

The tables highlighted in yellow represent the main entities in the data model: users, people, and movies. The tables highlighted in green represent the JOIN tables required to facilitate the many-to-many relationships between the entities.

There are two potential pitfalls here. First, let’s talk about naming. The example above is fairly straightforward, but say we have a many-to-many relationship in our data model between Products and Orders — an order may contain one or more products, and a product may appear in many orders. What do we call that table? order_products, order_line? This feels unnatural, and instantly you are adding tribal knowledge to the database, making it harder for others to understand.

When you use that table to find an actor for a particular movie, you start to hit the O(n) problem.

JOINs & The O(n) Problem

GraphQL is designed to be a flexible query language that allows you to retrieve an infinite level of nested values. The more nested items retrieved, the more joins are queried. Therefore the longer the query takes. Furthermore, the more data added to the database, the larger the underlying indexes become and the longer the query will take to return a result.

This is known as the Big O notation or O(n) notation — the number of computational resources required to compute the JOINs is relative to the size of the input data. The more data added to the database, the more data needs to be processed, and the slower the database will become.

Many relational databases support subqueries or window functions, but these must still be constructed in memory at query time, which can be an expensive operation.

This problem can be partially resolved by database tuning, partitioning, or denormalizing data to improve response times, at which point you’ll need to become a database expert.

In A Document Store

Document stores, such as MongoDB or CouchDB, differ from Relational databases in that they are designed to store unstructured or semi-structured data. Data is organized into collections, each of which consists of many documents. Each document in a collection represents a single record, which can have its own unique set of key-value pairs. This approach is more flexible than relational databases, but as the data is schema-less, you must be careful to enforce consistency through your application layer.

You would most likely create collections to store users, movies, and people.

Data Duplication for Query Performance

Document Stores can also fall foul of the O(n) problem. NoSQL databases, in general, are all designed to provide various their own solutions to the problems of read and write performance.

A common approach to solve the O(n) problem is to duplicate data across collections to speed up query responses. For example, the movies collection may store directors as an array of string values.

{
  "_id": ObjectId("63da26bc2e002491266b6205"),
  "title": "Toy Story",
  "released": "1996-03-22",
  "directors": ["Tom Lasseter"]
}

This is perfect if you only want to display the data within a UI. But if we need to ask more complex questions, for example, how many movies has Tom Lasseter directed? — things start to get complicated. Do we loop through every movie record and check the directors array for a name? What if two directors share the same name?

If you want to query across collections, you would usually store a reference to the unique ID of the record in the corresponding collection. Take the user example below: the ratings for that user can be stored as an array against the user document, making it easy to access. Each rating contains a reference (in this case, a MongoDB DBRef to reference the ObjectId of the document in the movies collection).

{
 "_id": ObjectId("63da267a89f7381acf7ab183"),
 "email": "john.doe@example.com",
 "name": "John Doe",
 "ratings": [
   {
     "movie": {
       "$ref": "movies",
       "$id": ObjectId("63da2681680f57e194eb3199"),
       "$db": "neoflix"
     },
     "rating": 5
   },
   {
     "movie": {
       "$ref": "movies",
       "$id": ObjectId("63da26b613fe29cf79d92e2f"),
       "$db": "neoflix"
     },
     "rating": 3
   },
 ]
}

Document stores support pipelines or map-reduce functions that allow you to compute the JOIN at read time. But these can become unwieldy quickly and hard to reason about, and take time to compute. These read-time JOINs also fall victim to the O(n) problem. Each reference must be looked up in an index to find the corresponding record, which must also be decoded. The larger the collection, the larger the index and the longer each lookup may take. Multiply that time and complexity by the number of nested items, and all of a sudden, we’ve got a slow and complicated pipeline or map/reduce function.

To avoid this complexity, you could also store some of the required properties for the movie, for example, the movie title, in the rating object.

You may also store the movie title as a key in the rating to balance out the ease of readability and data duplication. But now we also have to make difficult decisions on what data to duplicate to speed up. If the use case changes in any way, a mountain of work is required to fit the new use case. What if we want to query from movie to rating?

You may also want to fan out your writes, duplicating data across collections to speed up the read-time performance, but that also comes with its own maintenance headaches and a whole load of potential for technical debt.

The Case for Graphs

Now, let’s look at this data as a graph. The data structure of Nodes and Relationships fits this problem well. Rather than creating JOIN tables to handle many-to-many relationships or storing duplicated data for reference, the verbs in the use case are stored as relationships:

More Natural Modeling

The data model above is easier to understand and reason about. At a quick glance, you can see that a User may have one or more REVIEWED relationships pointing to a Movie node. Nodes and relationships can both contain properties stored as key-value pairs. We can use this to store the rating and createdAt properties of the review directly on the relationship.

Constant Query Times

Remember how I mentioned earlier that relationships are treated as first-class citizens? When a relationship is created in Neo4j, a pointer is appended to the node at each end of the relationship, ensuring that every node is aware of every relationship going out from or coming into it.

This enables the query engine to quickly lookup relationships without relying on an index. This ensures that query response times remain constant to the amount of the graph touched during the query rather than the data size overall.

Querying a Neo4j graph is also different from relational databases and document stores. Neo4j uses a proprietary language called Cypher. Cypher is similar in structure to SQL, but instead of starting with a SELECT statement and using JOINs to combine data, a Cypher statement begins with a MATCH clause, which defines a pattern of data to return.

Neo4j will then parse the query, examine the database schema and use database statistics to determine the most efficient way to traverse the pattern. Regardless of the way the pattern is written, the query will be executed in the same way.

Let’s look at the SQL and Cypher statements required to retrieve the data side by side. Both queries will find the names of actors from the movie The Matrix.

SQL Cypher
SELECT p.name, p.born, r.roles, m.title
FROM people p
INNER JOIN roles r on p.id = r.person_id
INNER JOIN movies m on r.movie_id = m.id
WHERE m.title = ‘The Matrix’
MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
WHERE m.title = ‘The Matrix’
RETURN p.name, p.born, r.roles

In a Cypher statement, you use an ASCII-art style syntax to draw the pattern you would like to read from the graph. Nodes are surrounded by parentheses ( ( and ) ), and relationships are drawn using dashes and an arrow to represent the direction. This declarative approach differs from a Pipeline in MongoDB, where you must express exactly how the data should be retrieved.

This is only a trivial example, but the more complex the use case becomes, the more a Cypher statement comes into its own. I have shown Cypher statements to business owners, architects, and even C-level executives, who have all quickly understood what the statement is doing, which cannot be said for an SQL statement and certainly cannot be said for a pipeline.

Suddenly the barrier to data engineering doesn’t seem so high.

Conclusion

My mantra has always been to use the best tool for the job, particularly when it comes to databases. You may feel that my opinion is a little biased, as I am literally paid to have this opinion. But since first installing Neo4j around a decade ago, I’ve started to see the value of connections everywhere.

A simple network of nodes and relationships is surprisingly powerful when storing data. Graph databases allow you to avoid much additional work to model your use case to work with a database and naturally handle performance and scale.

If you would like to learn more about Neo4j, you can check Neo4j GraphAcademy, where we have constructed Beginners courses that will give you the confidence to import and query data in Neo4j, and the Developer courses teach you will show you how to connect to Neo4j using one of the five official drivers: Java, JavaScript, Python, .NET, and Go.

You can create an AuraDB Free instance pre-populated with data, which will hold 200k nodes and 400k relationships, and it’s free for as long as you need.

So, if you are working with a complex, highly connected dataset or would like to futureproof your project against complicated database migrations and refactoring in the future, why not put the Graph into GraphQL?

Playfulness In Code: Supercharge Your Learning By Having Fun

I’m often asked where the ideas come from. How do I know the things I do? Having ten years of experience in development helps, but what supercharged my learning was pushing myself to build the things that came into my head, however unusual. I developed an appetite for building things that aren’t ‘the norm.’ With that mindset, every idea becomes an opportunity to try something new.

One of my main mantras is to make learning fun. It’s something people have come to know me by. Tuggable SVG light bulbs with GreenSock, Vincent van Git, Useless machines with React… plenty more besides. You can read the docs, you can follow the tutorials, but wouldn’t you be more motivated by trying to make something unique, something no one else has seen before?

Here’s how having fun can supercharge your learning. Throw a record on, pick a mood, and let’s get to it.

See the Pen Superstar DJ v3.0 w/ ScrollTrigger 😎 (Scroll to scratch!) by @jh3y.

Wanting To Learn

There is a big caveat to everything I’m about to say: if you’re not motivated to learn, you won’t learn. Even if you know it’s something you need to learn. The need is optional, but the want is not. Odds are that if you don’t want to do something, you’re not going to do it. After all, most of you reading this are likely out of school now. You’re not obligated to prepare for that exam or get that grade. You have your own free will.

In most cases, learning is driven by some goal or target. An extreme example would be the goal of paying your bills. "I must learn X for my job, to keep my job and pay my bills". This article isn’t about those scenarios. It’s about the times when it’s not necessary. (You can only rebuild your portfolio so many times, after all.)

I have to go back quite a bit to think about how I turned out to be writing this article. I wasn’t always an extracurricular learner or even a creative coder. I actually started out as a middleware developer. I finished my degrees, got my job, and I was happy doing the eight-hour day and leaving it there. It wasn’t until towards the end of my first role that I met the “front-end” and started dabbling in it.

The first thing I remember making was a basic Trello clone. It was an opportunity to try out HTML5 "Drag and Drop" and the contenteditable attribute. It was very basic and you could create tasks and move them about. I put it in a jsfiddle or jsbin and shared it. Some colleagues thought it was cool, and that was that. Unfortunately, I’ve lost that demo now, but here’s a quick recreation from memory.

See the Pen HTML5 Drag & Drop Task Board by @jh3y.

Fast forward a little and CSS animation and 3D transforms were on my radar. In fact, 3D CSS and animation were some of the first things I spent time playing with. One thing I started with was creating a collection of loading spinners. If I had a few moments, I’d mess about with different properties and see what I could make while adding them to a file all the time. Later, I’d turn it all into a GitHub project.

A pattern was emerging of me wanting to make things. And when an opportunity to try something came along, I’d pair that with an idea and see what happened. Further adjustments to that Trello clone got valuable feedback when I posted it on Hacker News. That spurred me to create new versions of it. I haven’t touched it for a few years, but it still lives over on Github.

A few side projects and some time after that came to a winking bear demo, which I posted on CodePen. CodePen was new to me at this point.

See the Pen Gricssly bear by @jh3y.

The next day, I was on a client site and someone said, "I saw your pen on the front page of CodePen! Nice!". I said "Thanks!", but I had no idea what that meant until I went and checked. And there was the winking bear! This was a catalyst for my "playfulness" with code, where the pattern flipped. I went from "I want to learn X, so how do I fit it into Y" to "I want to make Y, can I learn X to do it?".

That’s what motivates me and makes learning fun. It could work for you, too! Instead of the thought of learning X being the driving force, it’s the thought of making Y. The fact you’re learning new skills is a bonus. As my skills have developed, the ability to make my demos more and more "playful" is noticeable. But it all began from making things for the sake of making things and learning something. "How would you do that?" and not "How can you learn that?". As your skills develop, you too can become more playful with your code. And the two will complement each other.

Playful Coding

Where do all the ideas come from? Well, it’s a good question. We can’t force creativity, but there are things I can suggest that might help convince it to appear.

Document Everything

Get a notebook, start a Trello board, open a Notion account. Find a way to take notes of your ideas. No idea is a bad idea. Repeat. No idea is a bad idea. I write down every little spark that comes into my head. That’s why I’d suggest a digital solution you can install on your phone. You never know when you’ll have an idea, and it will be annoying the next day when you can’t remember it. Trust me, I’ve been there.

Here are five random things from my "List" that all trigger something for me:

  • Red and white toadstools;
  • Impossible checkbox spin-off;
  • Peter Griffin blinds in CSS;
  • Power-up screen bear glare huge parallax from the game documentary;
  • Bread Array slice/splice cartoon.

Some of that might make sense. Some of it might not. The important thing is to write down keywords that trigger thoughts of something I want to make. I can tell you the first idea is a Procreate drawing, and the fourth is from a show I watched on Netflix. There was a part in the show where a character’s face almost parallaxes on the screen. I thought it would make an amusing Twitch overlay if I can make it. On the list they go.

Another solution I’ve recently adopted and would also suggest, keep notebooks dotted about. One by the side of the bed is great! It means you don’t need to get out of bed to write down that idea you just had. Your note-taking needn’t be limited to ideas either. Document your processes and other things as you go. You’ll find that scribbling things down can often spark new ideas.

Sparking Ideas

That leads to "Where?". Where can you grab an idea from? The answer here is very cliché: anywhere! The more I speak about it with people, the more it feels like an instinct you refine. Plucking ideas out of nothing is something you train your mind to do over time.

To kickstart things, here’s a list of places you can go to start:

CodePen

CodePen is a great resource. Have a browse, see what people are making. Could you make something similar? Someone created an Elephant with CSS, can you create a Giraffe? CodePen does a weekly prompt via email challenging you to make something. There will be a theme or certain criteria and you can follow the tags to see what people are making. And then there’s the Spark, CodePen’s newsletter which will usually be full of cool things. There are loads of great demos on the site, people giving feedback. It’s an inspiring place.

See the Pen Tuggable Light Bulb! 💡(GSAP Draggable && MorphSVG) by @jh3y.

Media (TV, Books, Film)

You can get a lot of ideas from the media. Seen a cool TV advert? Can you recreate part of it? How about the opening credits of a film? Lots of things pop up that can spark a little creativity. Books are another great resource — fiction and nonfiction. I created this HSL slider after reading Refactoring UI:

See the Pen HSL Slider w/ React + CSS vars 🤓🎨 by @jh3y.

And this is from the closing credits of the Netflix series, “Love, Death, and Robots”:

See the Pen Love, Death & Robots outro w/ GSAP 🤓 by @jh3y.

Newsletters

Sign up for newsletters that interest you. You don’t have to read them all the time, but they’re there for you. I’ve already mentioned the CodePen one. Codrops is another great one for seeing a variety of demos. They also do an "Awesome Demos Roundup". CSS Tricks is another with great reads and resources. Or, of course, the Smashing newsletter.

This demo below was created due to a challenge set in the ViewBox newsletter. And the idea was itself inspired by the film Men in Black which I’d happened to watch twice that week.

See the Pen Orion’s Galaxy v2 by @jh3y.

Muzli

I love this one. Muzli is a browser extension that fills your "New Tab" screen with design inspiration. Have a browse through this when opening a new tab and you’re bound to find some ideas. They also do a roundup for various things over on Medium. I’ve often picked up ideas from looking through these. Such as this demo inspired by this roundup. RamBear was a recreation of this Dribbble shot from “Gigantic” with a bear spin on it.

See the Pen Code name: RamBear 😅 by @jh3y.

News & Seasonal

Current news and seasonal events are sure to get ideas firing. How about spooky demos for Halloween? I made this bear having an X-Ray because of a CodePen challenge set for Halloween.

See the Pen Bear gets an X-Ray w/ CSS Variables 🐻🔍 #CodepenChallenge by @jh3y.

Or remember when everything was cake? Yeah? I thought about making a 3D cake that you could interact with and it kinda went from there. My back catalog is full of demos that relate to current events.

See the Pen CSS is cake 🍰 (Tap the slices! 👇) by @jh3y.

Dribbble

Dribble is a great site for checking out other people’s creative work, and it could spark some ideas of your own. It’s not unusual to see people recreating things they’ve seen on Dribbble. That said, if you do recreation, please credit the original work. It’s not "inspiration" if you take the original, recreate it, and take the credit. You take the opportunity from others to discover work from the original author.

Reddit

I’m not a big Reddit user myself. But, you can sometimes find interesting animations and things in various sub-Reddits. /r/oddlysatisfying has had the occasional animation that I’ve recreated. This cubes animation was something I wanted to recreate. At the same time, I wanted to try GreenSock. So I paired the two and it was the first time I used GreenSock. Honestly, try searching for “oddlysatisfying cubes”.

See the Pen Cubed 😅 by @jh3y.

Years later, I’ve revisited this to build it in a different way. That allowed me to put a spin on it.

See the Pen Infinite Color Cubes by @jh3y.

Twitter

If you have a Twitter account, follow people who interest and inspire. They could be in a completely different field, but their work may well spark ideas for you. There are some fantastic accounts out there. One account that springs to mind is @beesandbombs. They upload real cool animations that often have optical illusions within them. I’ve often thought “I’ll make that,” and then proceeded to try some way of making it whether it be CSS, HTML5 Canvas, and so on. It’s a great way to train to work on the finer details.

pic.twitter.com/OZvKVo0ly1

— dave (@beesandbombs) November 10, 2020
Anywhere Else

I could keep listing sources of inspiration, but it can be different for everyone. These are the ones that work for me. But consider anything. Things you see on your travels, conversations, or things around the house.

Turning Ideas Into Demos And Projects

You’ve got your ideas. But, there’s no rush to make them. You don’t have to make everything you note down. In fact, odds are you’ll never have time to make everything. That’s something you have to deal with. It’s something I struggled with the better I got at documenting my ideas.

See the Pen LEGO Cyber Truck w/ three.js 😅🚙 by @jh3y.

If you browse my CodePen history it’s like a timeline for what I’ve been learning and exploring, driven by ideas and inspiration. The thought of making something, not learning something. I don’t usually have time to look back at old demos but this article has prompted that. It’s interesting to look back and remember what drove what.

For example, I wanted to create Masonry layouts, so I learned the technique for it using flex. I wanted to create star fields, so I learned HTML5 Canvas rendering techniques. In fact, I remember learning the latter in the mornings over breakfast.

See the Pen Randomly generated CSS lava lamp 💡 #CodePenChallenge by @jh3y.

This lava lamp was prompted by a CodePen challenge. I’d seen a bit about SVG filters but not had anything I wanted to try them out on. I wanted to make a lava lamp with CSS and it was a perfect opportunity.

Make for the sake of making. Don’t overthink it. Be driven by the idea because you will learn things. You’ll probably learn a lot more things than you ever expected. It can and will strengthen your ability to rise to a challenge or switch context at the drop of a hat. These are skills that can really empower your career as a developer.

Document your ideas and when you want to make them, go for it! If your first focus is the “How” or the “Why”, that idea might stick around on your list for some time.

Don’t Dwell On The ‘Why’ And ‘How’

I make a lot of ‘whimsical’ things and I am often asked, “Why?”, “Is there any practical use for this?”, and so on. Don’t dwell on that side of things. You’re making something because you want to. Making something unconventional can be more fun than following “Build a TODO app 101”. There’s a time and a place for the 101s, but I want you to enjoy learning. Gain an appetite for creating wonderful things that none of us have ever seen.

Work on the ideas that spark joy for you. Don’t let the "How?" distract you. Focus on the "What?". The goal is to get the idea, then find a way to make it. If it means learning something new — great. If you can do it with something already in your toolbelt — awesome. Let the ideas guide you. The variety of your projects can often challenge you to use tools you already know in different ways. You can pick up new techniques from tackling problems others might not have even seen. It gives you an ability to think “Outside of the box”.

Let’s also address the idea that these things aren’t ‘useful’. I don’t believe this is ever the case. A major example for me is CSS art. “Why do this with CSS? Use an image like SVG”. Don’t buy into that. By drawing something with CSS, you level up your skills by creating interesting shapes, learning the stacking index, and so much more. The cool thing with CSS art, in particular, is that every creation tends to yield a different problem. Yes, you won’t be dropping that 1000 lines of CSS into a production site anytime soon and you’ll use an image. But, did the image teach you how to use clip-path or be a wizard with border-radius?

For example, a demo of mine is "The impossible checkbox". It’s a toggle that when you toggle on, a bear turns off. The more you turn it on, the angrier the bear gets. If I had focused on the “How?” then that demo may never have come to life. Instead, I sketched out what I thought might look like. And then decided I was going to use React and GreenSock together with SVG.

See the Pen Impossible Checkbox v2 🐻 by @jh3y.

Don’t let the idea of “How?” deter you from the “What?”. Also, never question the “Why?” Make cool things and you will learn from them, no doubt.

Make, Make, Make

Start writing down your ideas and making things for the sake of making things. That’s my advice if you want to level up and add some playfulness to your code.

What you learn will find its way back into your work. As a recent example, I put together an eBook on CSS animations. I could’ve created every demo with a red square, but that’s not very engaging. Instead, the book has animated bunnies, racecars and UFOs to help the knowledge stick. Instead of trying to remember what the red square was doing and how. It’s "Remember we made the bunnies all jump at different times using animation-delay".

See the Pen Bouncing Bunnies (animation-delay lesson) 😎 by @jh3y.

This is the major point. Being playful with your code and what might seem like “lateral” learning can be a huge driving factor in evolving your skills. It might not be noticeable at once, but every time you make some new whimsical thing, you’re leveling up!

Grab a notebook, download a note-taking app (Notion, Trello, Keep), and start documenting your ideas. Training yourself to write down ideas. However big, however small, write them down. Create ideas from things that interest you. Hoard inspiration. Sign up for newsletters. They don’t have to be tech-related. Give muz.li a try. Read a book, watch a film. Bookmark Dribbble, perhaps.

And when the moment strikes, start making! Struggle with the “How”? Try different methods, check out how others do things, reach out to people online. Every step teaches you something new. Besides, isn’t fun worth having for its own sake anyway?

In Defense of the Ternary Statement

Some months ago I was on Hacker News (as one does) and I ran across a (now deleted) article about not using if statements. If you’re new to this idea (like I was), you’re in a for a real treat. Just search for "if statements" on Hacker News. You'll get articles proposing that you might not need them, articles that refer to them as a code smell and even the quintessential "considered harmful." Listen, you know a programming concept is legit when people start suggesting that using it is actually gonna hurt somebody.

And if that's not enough for you, there is always the "Anti-If Campaign." If you join, you get a nifty banner and your name on the website. IF you join. Oh the sweet, sweet irony.

The first time that I ran across this bizarre "if anathema" phenomenon, I thought it was interesting, but probably just more people mad on the internet. You are always one Google search away from finding someone who is mad about anything. Like this person who hates kittens. KITTENS.

Some time later, I was watching Linus Torvald's TED interview. In that interview, he shows two slides. The first slide contains code that he deems is "bad taste."

And the second is that same code, but in what Linus would consider, "good taste."

I realize that Linus is a bit of a polarizing figure, and you might not agree with the "good taste" vs. "bad taste" phrasing. But I think we can universally agree that the second slide is just easier on the old eye balls. It's concise, has fewer logical paths to follow, and contains no if statement. I want my code to look like that. It doesn't have to be some genius algorithm (it never will be), but I think it can be clean, and remember what Billy Corgan of Smashing Pumpkins said about cleanliness...

Cleanliness is godliness. And god is empty. Just like me.

- Billy Corgan, "Zero"

So dark! But what an amazing album.

Aside from making your code look cluttered, if statements, or "branching logic," requires your brain to hold and evaluate two separate paths at one time along with all of the things that might occur on those paths. If you nest if statements, the problem intensifies because you are creating and tracking a decision tree and your brain has to bounce around all over that tree like a drunk monkey. This kind of thing is what makes code hard to read. And remember, you should write your code thinking of the moron who comes after you that is going to have to maintain it. And that moron is probably you.

As my own favorite moron, I've been making a conscious effort lately to avoid writing if statements in my JavaScript. I don't always succeed, but what I've noticed is that at the very least, it forces me to think about solving the problem from an entirely different angle. It makes me a better developer because it compels me to engage a part of my brain that is otherwise sitting on a beanbag eating peanut M&M's while the if statement does all the work.

In the process of not writing if statements, I’ve discovered my love for the way JavaScript lets you compose conditional logic with ternary statements and logical operators. What I would like to propose to you now is that ternary has gotten a bad rap, and you can use it along with the && and || operators to write some pretty concise and readable code.

The much maligned ternary

When I first started as a programmer, people used to say, "Never use a ternary. They are too complex." So I didn’t use them. Ever. I never used a ternary. I never even bothered to question whether or not those people were right.

I don't think they were.

Ternaries are just one-line if statements. Suggesting that they are implicitly too complicated in any form is just... not true. I mean, I'm not the frostiest donut in the box, but I have no problems at all understanding a simple ternary. Is it possible that we are infantilizing ourselves here just a tad when we say to always avoid them. I think that a well-structured ternary beats an if statement every time.

Let’s take a simple example. Say we have an application where we want to test and see if the user is logged in. If they are, we send them to their profile page. Otherwise, we send them to the home page. Here is the standard if statement to do that...

if (isLogggedIn) {
  navigateTo('profile');
}
else {
  navigateTo('unauthorized');
}

That's a damn simple operation to split out over six lines. SIX LINES. Remember that every time you move through a line of code, you have to remember the code that came above it and how it affects the code below it.

Now the ternary version...

isLoggedIn ? navigateTo('profile') : navigateTo('unauthorized');

Your brain only has to evaluate one line here, not six. You don’t have to move between lines, remembering what was on the line before.

One of the drawbacks to the ternary, though, is that you cannot evaluate for only one condition. Working from the previous example, if you wanted to navigate to the profile page if the user was logged in, but take no action at all if they weren't, this won't work...

// !! Doesn't Compile !!
logggedIn ? navigateTo('profile')

You would have to write out an actual if statement here. Or would you?

There is a trick that you can use in JavaScript when you only want to evaluate one side of the condition and you don't want to use an if statement. You do this by leveraging the way JavaScript works with the || (or) and && (and) operators.

loggedIn && navigateTo('profile');

How does that work!?

What we're doing here is asking JavaScript, "Are both of these things true?" If the first item is false, there is no reason for the JavaScript virtual machine to execute the second. We already know that both of them aren't true because one of them is false. We're exploiting the fact that JavaScript won't bother to evaluate the second item if the first one is false. This is the equivalent of saying, "If the first condition is true, execute the second."

Now what if we wanted to flip this around? What if we wanted to navigate to the profile page only if the user is not logged in? You could just slap a ! in front of the loggedIn variable, but there is another way.

loggedIn || navigateTo('profile');

What this says is, "Are either of these things true?" If the first one is false, it has to evaluate the second to know for sure. If the first one is true though, it will never execute the second because it already knows that one of them is true; therefore the whole statement is true.

Now, is that better than just doing this?

if (!loggedIn) navigateTo('profile');

No. In that form, it is not. But here’s the thing: once you know that you can use the && and || operators to evaluate equality outside of if statements, you can use them to vastly simplify your code.

Here is a more complex example. Say we have a login function where we pass a user object. That object may be null, so we need to check local storage to see if the user has a saved session there. If they do, and they are an admin user, then we direct them to a dashboard. Otherwise, we send them to a page that tells them they are unauthorized. Here is what that looks like as a straight-up if statement.

function login(user) {
  if (!user) {
    user = getFromLocalStorage('user');
  }
  if (user) {
    if (user.loggedIn && user.isAdmin) {
      navigateTo('dashboard');
    }
    else {
      navigateTo('unauthorized');
    }
  }
  else {
    navigateTo('unauthorized');
  }
}

Ouch. This is complicated because we're doing a lot of null condition checking on the user object. I don't want this post to be too strawman-y, so let's simplify this down since there is a lot of redundant code here that we would likely refactor into other functions.

function checkUser(user) {
  if (!user) {
    user = getFromLocalStorage('user');
  }
  return user;
}

function checkAdmin(user) {
  if (user.isLoggedIn && user.isAdmin) {
    navigateTo('dashboard');
  }
  else {
    navigateTo('unauthorized');
  }
}

function login(user) {
  if (checkUser(user)) {
    checkAdmin(user);
  }
  else {
    navigateTo('unauthorized');
  }
}

The main login function is simpler, but that's actually more code and not necessarily “cleaner” when you consider the whole and not just the login function.

I would like to propose that we can do all of this in two lines if we forgo the if statements, embrace the ternary, and use logical operators to determine equality.

function login(user) {
  user = user || getFromLocalStorage('user');
  user && (user.loggedIn && user.isAdmin) ? navigateTo('dashboard') : navigateTo('unauthorized')
}

That's it. All of that noise generated by if statements collapses down into two lines. If the second line feels a bit long and unreadable to you, wrap it so that the conditions are on their own line.

function login(user) {
  user = user || getFromLocalStorage("user");
  user && (user.loggedIn && user.isAdmin)
    ? navigateTo("dashboard")
    : navigateTo("unauthorized");
}

If you are worried that maybe the next person won't know about how the && and || operators work in JavaScript, add some comments, a little white space and a happy tree. Unleash your inner Bob Ross.

function login(user) {
  // if the user is null, check local storage to
  // see if there is a saved user object there
  user = user || getFromLocalStorage("user");
  
  // Make sure the user is not null, and is also
  // both logged in and an admin. Otherwise, DENIED. 🌲
  user && (user.loggedIn && user.isAdmin)
    ? navigateTo("dashboard")
    : navigateTo("unauthorized");
}

Other things you can do

While we’re at it, here are some other tricks you can play with JavaScript conditionals.

Assignment

One of my favorite tricks (which I used above), is a one-liner to check if an item is null and then reassign it if it is. You do this with an || operator.

user = user || getFromLocalStorage('user');

And you can go on forever like this...

user = user || getFromLocalStorage('user') || await getFromDatabase('user') || new User();

This also works with the ternary...

user = user ? getFromLocalStorage('user') : new User();

Multiple conditions

You can provide multiple conditions to a ternary. For instance, if we want to log that the user has logged in and then navigate, we can do that without needing to abstract all of that into another function. Wrap it in some parentheses and provide a comma.

isLoggedIn ? (log('Logged In'), navigateTo('dashboard')) : navigateTo('unauthorized');

This also works with your && and || operators...

isLoggedIn && (log('Logged In'), navigateTo('dashboard'));

Nesting ternary expressions

You can nest your ternary expressions. In his excellent article on the ternary, Eric Elliot demonstrates that with the following example...

const withTernary = ({
  conditionA, conditionB
}) => (
  (!conditionA)
    ? valueC
    : (conditionB)
    ? valueA
    : valueB
);

The most interesting thing Eric is doing there is negating the first condition so that you don’t end up with the question marks and colons together, which makes it harder to read. I would take this a step further and add a little indentation. I also added the curly braces and an explicit return because seeing one parenthesis and then immediately another makes my brain start to anticipate a function invocation that is never coming.

const withTernary = ({ conditionA, conditionB }) => {
  return (
    (!conditionA)
      ? valueC  
      : (conditionB)
        ? valueA
        : valueB
  )
}

As a general rule, I think that you should consider not nesting ternaries or if statements. Any of the above articles on Hacker News will shame you into the same conclusion. Although I’m not here to shame you, only to suggest that perhaps (and just maybe) you will thank yourself later if you don’t.


That’s my pitch on the misunderstood ternary and logical operators. I think that they help you write clean, readable code and avoid if statements entirely. Now if only we could get Linus Torvalds to sign off on all this as being “good taste.” I could retire early and and live the rest of my life in peace.

The post In Defense of the Ternary Statement appeared first on CSS-Tricks.