Using Logical Operators To Perform Ternary Operations In Javascript

Posted November 18, 2009 at 10:13 AM by Ben Nadel

Tags: Javascript / DHTML

Yesterday on Twitter, Cody Lindley pointed out that logical operators in Javascript (&& and ||) don't simply return True or False - they return specific operands used in the logical statement. While I have leveraged this concept in the past with statements like this:

  • // Logical OR in assignment.
  • var options = (options || defaultOptions);
  •  
  • // Logical AND in testing.
  • if (window.console && window.console.log){ .. }

... it was still hard for me to wrap my head fully around what the logical operators were really doing. If you look at the Mozilla Development Center documentation on logical operators, you will see that not only is the statement being evaulated for its truth, but one of the operands is returned based on that truth, even if the returned operand is NOT true. To help understand this, you have to stop thinking in terms of individual operand truths and concentrate on the statement-level truth (regardless of the individual values).

Thinking this way helped me clarify the situation in my mind (pseudo code):

UPDATE: This is not quite accurate; (a && b) retuns "a" only if "a" can be converted to false.

  • // Logical AND. ------
  •  
  • if (a && b){
  • return( b );
  • } else {
  • return( a );
  • }
  •  
  • // Logical OR. ------
  •  
  • if (a){
  • return( a );
  • } else {
  • return( b );
  • }

When you start to look at it this way, you think less about what values "a" and "b" contain and more just about how the AND / OR statements will be evaluated.

Even with this mentality, I still felt a bit fuzzy on how things were really working. As such, I needed an exercise that would force it into my brain. That's when I came up with the fun idea of duplicating the ternary operator in Javascript using logical operators. Here's what I came up with:

UPDATE: This does not quite work; will not return proper value if ifTrue is a falsey.

  • <script type="text/javascript">
  •  
  • // This method will use a simple tertiary operator to
  • // gather the description information based on the name.
  •  
  • var easyIIF = function( girl, ifTrue, ifFalse ){
  • return(
  • ((girl == "Tricia") ? ifTrue : ifFalse)
  • );
  • };
  •  
  •  
  • // This method will use a combination of logical operators to
  • // gather the description information based on the name.
  •  
  • var hardIIF = function( girl, ifTrue, ifFalse ){
  • return(
  • ((((girl == "Tricia") && ifTrue) || ifTrue) || ifFalse)
  • );
  • };
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • // Set the name of the girl.
  • var girl = "Tricia";
  •  
  • // Output using easy tertiary operator.
  • document.write(
  • easyIIF( girl, "Hot", "Nice" ) +
  • "<br />"
  • );
  •  
  • // Output using complex logical operators.
  • document.write(
  • hardIIF( girl, "Hot", "Nice" ) +
  • "<br />"
  • );
  •  
  • </script>

What I created above is two different IIF() style methods that perform a ternary operation of sorts; the first one, easyIIF() used the actual ternary operator while the second one, hardIIF(), uses a combination of logical operators to duplicate the same functionality. When we run the above code, we get the following output:

Hot
Hot

This is pretty fascinating stuff... and a bit terrifying; using logical operators to perform more than simple boolean evaluation is nowhere near readable and requires the programmer to have an exhaustive understanding of how logical operators work. I wrote this post as an exercise - not to recommend that people use this approach (except in the most commonly understood use-cases).



Reader Comments

Nov 18, 2009 at 10:25 AM // reply »
1 Comments

The 'fuzziness' was in my head too... and this cleared it! Thank you :)


Nov 18, 2009 at 10:29 AM // reply »
11,314 Comments

@Guganeshan.T,

Ha ha, sweeet.


Nov 18, 2009 at 10:31 AM // reply »
8 Comments

That is interesting stuff. I sure hope, though, that I never have to work on code where someone decided to substitute that approach for ternary operators! It's just not at all readable to me, and would be easy for me to get outputs flipped in my mind as I'm working through the logic. You should burn this blog post immediately. lol


Nov 18, 2009 at 10:33 AM // reply »
11,314 Comments

@Doug,

Right?!? Even after writing it, I have to remind myself what it's doing. It's all about the short-circuit evaluation on the || :)


Nov 18, 2009 at 11:03 AM // reply »
1 Comments

hi, I just thought "huh?" but then when I viewed the script closely, I became gradually clear what you mean ^ ^ is not at easy to understand, I really must admit ^ ^


Nov 18, 2009 at 11:05 AM // reply »
211 Comments

I've already been doing the logical OR ( var options = (options || defaultOptions); ) and always felt like I was cheating or doing something I shouldn't be doing.


Nov 18, 2009 at 11:07 AM // reply »
211 Comments

Now that Ben & Cody blogged about it, I can sleep better at night.


Nov 18, 2009 at 11:08 AM // reply »
19 Comments

Think about it this way:

When || is encountered, if the preceding expression was truthy, evaluation stops and that truthy value is returned.

When && is encountered, if the preceding expression was falsy, evaluation stops and that falsy value is returned.

Otherwise, things continue getting evaluated.

A truthy value is anything that == true, like 12 or "foo" or true, while a falsy value is anything that == false, like 0 or '' or undefined.

Where this is great is when you need to "test your way" into a property. For example:
var log = window.console && console.log;

1) window.console: truthy
2) &&: the preceding thing wasn't falsy, so continue.
3) console.log: it's the last statement, so its value is returned.

If you just tried this and console didn't exist, you'd get some errors:
var log = console.log;

Also, this..
var result = something ? something : otherthing;

..can be more succinctly written as:
var result = something || otherthing;

And because logical || and && short-circuit, that means when evaluation stops, nothing afterwards gets evaluated..

Meaning that in this example, if something is truthy, somefunction() will never get executed:
var result = something || somefunction();

Now, if you've made the "logical" leap here.. you can see that logical || and && can also be used.. to conditionally execute functions.

This..
something || somefunction();

..is equivalent to:
if ( !something ) { somefunction(); }

And this..
something && somefunction();

..is equivalent to
if ( something ) { somefunction(); }

So now you know what this code does:
window.console && console.log( "i'm only logged if window.console exists!" );

Either way, the end goal should be to write more readable, maintainable code though.. so if it looks complicated to you while writing it, it'll probably be about 5x worse to someone reading it six months from now, you know, someone like you :)


Nov 18, 2009 at 11:11 AM // reply »
11,314 Comments

@Sophie,

Yeah, funky stuff right?

@Todd,

I think that's one of those common use-cases that we all use. There are going to be nuances of complexity. Like, I think, as you are saying, that:

var a = (b || c);

... is pretty common. But, what about:

var a = (b && c);

... this gets a bit more fuzzy in my head (especially if you think that "a" will now hold a boolean).

But what about this?

var logger = ((window.console && window.console.log) || window.alert);

... definitely complex, but you sort of get the idea of what's going on.

I guess you just have to treat each situation as a judgment on usability and readability.


Nov 18, 2009 at 11:14 AM // reply »
11,314 Comments

@Cowboy,

Ha ha, we both used the console.log example at the same time :) Really good comment.


Nov 18, 2009 at 11:22 AM // reply »
19 Comments

@Ben, this is a perfect example of where *not* to use logical && and ||:

var logger = ((window.console && window.console.log) || window.alert);

What if console.log returns undefined? Then it logs AND alerts. I'm guessing that's not what you want.

This is probably what you want:
var logger = window.console ? console.log : alert;

Also, as an aside, note that you can't reference console.log in this way in all browsers. You should actually do:

var logger = window.console ? function(){ console.log.apply( console, arguments ); } : alert;

.. but that's why I've created a cross-browser console.log debugging wrapper:
http://benalman.com/projects/javascript-debug-console-log/


Nov 18, 2009 at 11:30 AM // reply »
11,314 Comments

@Cowboy,

I wasn't necessarily saying that you should do that - I was just pointing out that sometimes the complexity of a statement is made somewhat clear based on the items being referenced.

But, I am not sure what you are saying referring the undefined value? If console.log returns undefined, then the first statement would become false, and hence, window.alert would be returned.

I tested this in Internet Explorer, which doesn't have console.log and it went to the alert. Even if it's only partially defined:

window.console = {};
var logger = ((window.console && window.console.log) || window.alert);
alert( logger );

... IE still defers to window.alert.

I am not sure what you mean. Also, I am not sure how it can both "logs AND alerts" - can you explain that further? Wouldn't that require some sort of concatenation of functionality?


Nov 18, 2009 at 11:33 AM // reply »
11,314 Comments

@Cowboy,

Your cross-browser logger looks badass. I'll have to take a close look at that. Thanks for posting it!


Nov 18, 2009 at 11:38 AM // reply »
5 Comments

I listed out a few examples of these in my jQuery Anti-Patterns talk. (They're a good thing, in my opinion; and can save quite a few bytes).

http://paulirish.com/perf/ <== Slide 54 of jQuery AntiPatterns slides
http://gyazo.com/8893db96d6d18b90b2a2ac150f4dd6cd.png <== Screenshot of that slide

It's just important to commment so noobs know what the heck you're doing. It's not the easiest code to understand. :)


Nov 18, 2009 at 11:41 AM // reply »
19 Comments

@Ben,

Sorry for the confusion.. the problem would be if you were actually invoking the function like:

var value = "foo";
window.console && console.log( value ) || alert( value );

In that case it would both log and alert, since even though console.log exists, when it's invoked it returns undefined, which is falsy.

Either way, a ternary removes all ambiguity:
window.console ? console.log( value ) : alert( value );

Still, you can't reference console.log like that win Webkit, it'll assplode all over the place.


Nov 18, 2009 at 11:46 AM // reply »
11,314 Comments

@Cowboy,

Ahhh, gotcha - sorry for the misunderstanding. I hate when Webkit assplodes... no one wants to start their day off that way :)

@Paul,

I'll take a look; sounds like maybe a good lead into one of your anti-pattern segments on YayQuery ;)


Nov 18, 2009 at 11:47 AM // reply »
11 Comments

Nice post Ben. I think it's okay to use basic stuff like:

function runFn(fn) {
return fn && typeof fn === 'function' && fn();
}

It may not seem readable to a JS beginner, but frankly, I write code assuming that another developer reading it will be proficient in the area. For me, a&&b&&b() is more readable than:

if(a){if(b){b()}}

Another interesting aspect of this topic is operator precedence and associativity, I discussed both in this post: http://james.padolsey.com/javascript/express-yourself/

E.g.

Does "a&&b||c" really mean "a&&(b||c)" or "(a&&b)||c" ... They are both very different. In this case, since &&'s precedence is higher than ||, it would be: "(a&&b)||c".

It's also interesting to note that expressions with multiple operators will sometimes be processed from right-to-left instead of the conventional left-to-right. For example, the ternary operator (a?b:c), if encountered more than once in a single expression, will evaluate from right-to-left:

a ? b : c ? d : e

is the same as:

a ? b : (c ? d : e)

but not the same as:

(a ? b : c) ? d : e

This is actually quite important -- it can cause a lot of confusion sometimes.


Nov 18, 2009 at 11:49 AM // reply »
19 Comments

Another example (that Paul and I have already discussed) of where function invocation can be an issue with logical || and && is in the slide he just mentioned:

data = window.JSON && JSON.parse(data) || eval('('+data+')');

If window.JSON exists, JSON.parse(data) is called.. but what if the initial data is "0"? That's valid JSON, and when parsed returns 0.. which is falsy. So at that point, eval('('+data+')') is actually called even though the JSON has already successfully been parsed.

Why not use:
data = window.JSON ? JSON.parse(data) : eval('('+data+')');

Well, if JSON.parse doesn't exist, you'll get an error. But you'd actually get that same error in the initial code.

So why not use:
data = (window.JSON && JSON.parse) ? JSON.parse(data) : eval('('+data+')');

It's the best of both worlds, logical && and a ternary!


Nov 18, 2009 at 12:01 PM // reply »
11,314 Comments

@James,

Case in point why I wake up happy every day that I have the option to litter my code with parenthesis - zero confusion on order of operations.

@Cowboy,

I think I vaguely remember Paul talking about that in his jQuery Performance talk. Maybe it was different (haven't looked at the slide yet), but I remember him talking about something where a result of zero would cause an issue.

I like your last example - to me, the combination of ternary and logical seems like the most clear.


Nov 18, 2009 at 12:04 PM // reply »
10 Comments

With the ternary-ish trick, you can force the first branch to always return true (and therefore always skip the second) regardless of what the first branch's expression evaluates to:

(exprTest && !(exprTestTrue && false)) || exprTestFalse;

ternary equivalent:
exprTest ? exprTestTrue : exprTestFalse;


Nov 18, 2009 at 12:19 PM // reply »
10 Comments

heh - you can take advantage of string truthiness and make it even shorter (and more unreadable) with this:

exprTest && (exprTestTrue || ' ') || exprTestFalse;


Nov 18, 2009 at 12:21 PM // reply »
11,314 Comments

@Jeremy,

Ha ha, you are a mad man. I had to read your first statement like 8 times before it made sense. Too much partial-result storage in my head.


Nov 18, 2009 at 12:51 PM // reply »
35 Comments

This exercise (and the resulting conversation) is one of the cool but frustrating things about programming to me.

It's cool to be able to write code to do things like you're discussing here: sometimes you can wrap up something that took 10-15 lines and compress it to 1-2 lines. The frustrating part is realizing that after you do that, no one else is going to be able to understand it, and if anything ever goes wrong with that part of the code, people are going to track you down to fix it ...


Nov 18, 2009 at 12:57 PM // reply »
10 Comments

OK - last one, but this is too hot not to share (worked in all my tests):

exprTest && exprTestTrue|1 || exprTestFalse;

bitwise ops FTW!


Nov 18, 2009 at 1:29 PM // reply »
11,314 Comments

David Gault just pointed out a typo in my example - easyIIF() was called twice; the second one should have been hardIIF(). The result is the same.

@Dave,

I hear what you're saying. I would err on the side of readability, when in doubt.

@Jeremy,

Bit-wise operations make my brain hurt - you are a mad scientist.


Nov 18, 2009 at 1:36 PM // reply »
11,314 Comments

Hold on.... I'm thinking this doesn't work now (specifically my example). If the ifTrue value is false. Working on it now.


Nov 18, 2009 at 3:09 PM // reply »
11,314 Comments

Regardless of the good conversation we've had here, I put some updates into the blog entry (in red) to signify some slight misunderstandings I had.


Nov 19, 2009 at 5:42 AM // reply »
1 Comments

hello ben. it's a funny script, but why do not create a rubric and give us a little more of these scripts, i think it would be fun to read ^ ^


Nov 19, 2009 at 10:12 AM // reply »
11,314 Comments

@Julius,

I am not sure what a rubric is? Can you explain further?


Nov 19, 2009 at 11:38 AM // reply »
132 Comments

In excess it certainly reduces readability, but the idea that the operators return a value is pretty foundational to some languages.

For instance, ruby doesn't have a ternary operator, instead you use the logical operators.

An example from a recent project was:

# username is the first command line argument if it exists
# or instead we prompt for the username
username = ARGV[1] or Prompt.prompt("Username: ")

And another example where we emulate the ternary operator:

# format a URL for doing an http request where we need an int
URI.encode("reusetoken=#{reusetoken and 1 or 0}")

This certainly feels more readable than ?:

I was pretty disappointed to see the ternary operator added to CF9. I had hoped that the boolean operators would be uncrippled so that you'd no longer get the "X cannot be converted to a boolean" errors and instead get more JS like behavior. Perhaps in a later version.

Think:

#user.getEmail() or "Not Provided"#

vs

#len(user.getEmail()) ? user.getEmail() : "Not Provided"#


Nov 19, 2009 at 1:21 PM // reply »
11,314 Comments

@Elliott,

I personally like the ternary operator, but that's just a personal opinion.

Going back to the example you have regarding email, I made this just for you :) Hopefully it doesn't give you a heart attack:

<cffunction name="getFirstName">
<cfreturn "Ben" />
</cffunction>

<cffunction name="getLastName">
<cfreturn "" />
</cffunction>

<!--- Get the first name, or default to "Not Provided". --->
<cfset evaluate( "len( setVariable( 'firstName', getFirstName() ) ) || len( setVariable( 'firstName', 'Not Provided' ) )" ) />

<!--- Get the last name, or default to "Not Provided". --->
<cfset evaluate( "len( setVariable( 'lastName', getLastName() ) ) || len( setVariable( 'lastName', 'Not Provided' ) )" ) />

<!--- Output name values. --->
First Name: #firstName#<br />
Last Name: #lastName#

... When you run this, you get:

First Name: Ben
Last Name: Not Provided

... I feel so dirty now.


Nov 19, 2009 at 1:35 PM // reply »
132 Comments

@Ben

Hah! You should start a CF obfuscation contest.


Nov 19, 2009 at 1:37 PM // reply »
11,314 Comments

@Elliott,

Ha ha ha, it might be too rough on my stomach ;)



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Jun 20, 2013 at 3:15 AM
A Billion Wicked Thoughts By Ogi Ogas And Sai Gaddam
nice post i love it thanks 4 u :) ... read »
seb
Jun 20, 2013 at 2:32 AM
Working With Inherited Collections In AngularJS
@mike, @ben, The best article about scope and prototypal prototypical inheritance in angularjs is http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical- ... read »
Jun 20, 2013 at 2:17 AM
ColdFusion NumberFormat() Exploration
Nice read thanks Ben, Is there a way to mask a negative number? Long story short in the finance sector when you go 'short' on a stock you want the price to fall this is a good thing because you are ... read »
Jun 20, 2013 at 1:09 AM
The Beauty Of The jQuery Each() Method
my html code : <html> <head> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="nss.js"> ... read »
Jun 19, 2013 at 11:31 PM
Directive Link, $observe, And $watch Functions Execute Inside An AngularJS Context
@Ben, bunch to learn indeed, but thats fun part : ) ... read »
Jun 19, 2013 at 10:41 PM
Referencing ColdFusion Query Columns In A Loop Using Both Array And Dot Notation
Burdock-roots Are you going fat day by day? You need to be good for your family and make some money too. So we bring for you a best product that helps you to be more energetic every day. You will b ... read »
Jun 19, 2013 at 9:52 PM
Working With Inherited Collections In AngularJS
I recognize the applicability of your solution, and how easy it makes to share data across multiple views or even "submodules" of rather simple application. But it seems to me that it creat ... read »
Jun 19, 2013 at 9:38 PM
Directive Link, $observe, And $watch Functions Execute Inside An AngularJS Context
@Alesei, Glad you like it. Even after working with AngularJS for months, I still get a bunch of unexpected, "$digest is already in progress". So hard to debug sometimes! ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools