[{"data":1,"prerenderedAt":947},["ShallowReactive",2],{"blog-\u002Fblog\u002Fjavascript-optional-chaining-nullish-coalescing":3},{"id":4,"title":5,"body":6,"description":932,"difficulty":933,"extension":934,"framework":935,"frameworkSlug":936,"meta":937,"navigation":442,"order":112,"path":938,"qaPath":939,"seo":940,"stem":941,"subtopic":942,"topic":943,"topicSlug":944,"updated":945,"__hash__":946},"blog\u002Fblog\u002Fjavascript-optional-chaining-nullish-coalescing.md","Optional Chaining & Nullish Coalescing in JavaScript — Safe Access Done Right",{"type":7,"value":8,"toc":917},"minimark",[9,14,48,52,74,138,152,156,161,211,221,225,235,257,266,270,284,326,329,354,364,395,483,502,506,523,570,587,596,617,696,699,703,719,760,770,774,780,814,822,826,910,913],[10,11,13],"h2",{"id":12},"tackling-cannot-read-property-of-undefined","Tackling \"cannot read property of undefined\"",[15,16,17,18,22,23,31,32,38,39,42,43,47],"p",{},"Few errors are as familiar as ",[19,20,21],"code",{},"TypeError: Cannot read properties of undefined",". It happens\nwhenever you reach into a nested structure that isn't fully present. ES2020 introduced two\noperators that, together, make safe access and sensible defaults clean and concise: ",[24,25,26,27,30],"strong",{},"optional\nchaining (",[19,28,29],{},"?.",")"," and ",[24,33,34,35,30],{},"nullish coalescing (",[19,36,37],{},"??",". Used well, they replace verbose guard\nchains and subtle ",[19,40,41],{},"||"," bugs. Used carelessly, they can hide real problems. This guide covers\nboth, how they combine, and when ",[44,45,46],"em",{},"not"," to use them.",[10,49,51],{"id":50},"optional-chaining-basics","Optional chaining basics",[15,53,54,55,59,60,70,71,73],{},"The ",[24,56,57],{},[19,58,29],{}," operator accesses a property only if the value before it is ",[24,61,62,63,66,67],{},"not ",[19,64,65],{},"null"," or\n",[19,68,69],{},"undefined",". Otherwise the whole expression short-circuits to ",[19,72,69],{}," instead of throwing.",[75,76,81],"pre",{"className":77,"code":78,"language":79,"meta":80,"style":80},"language-js shiki shiki-themes github-light github-dark","const user = { profile: { name: 'Ada' } }\nuser.profile?.name       \u002F\u002F 'Ada'\nuser.account?.balance    \u002F\u002F undefined no error, even though account is missing\nuser.account.balance     \u002F\u002F TypeError without ?.\n","js","",[19,82,83,110,120,129],{"__ignoreMap":80},[84,85,88,92,96,99,103,107],"span",{"class":86,"line":87},"line",1,[84,89,91],{"class":90},"szBVR","const",[84,93,95],{"class":94},"sj4cs"," user",[84,97,98],{"class":90}," =",[84,100,102],{"class":101},"sVt8B"," { profile: { name: ",[84,104,106],{"class":105},"sZZnC","'Ada'",[84,108,109],{"class":101}," } }\n",[84,111,113,116],{"class":86,"line":112},2,[84,114,115],{"class":101},"user.profile?.name       ",[84,117,119],{"class":118},"sJ8bj","\u002F\u002F 'Ada'\n",[84,121,123,126],{"class":86,"line":122},3,[84,124,125],{"class":101},"user.account?.balance    ",[84,127,128],{"class":118},"\u002F\u002F undefined no error, even though account is missing\n",[84,130,132,135],{"class":86,"line":131},4,[84,133,134],{"class":101},"user.account.balance     ",[84,136,137],{"class":118},"\u002F\u002F TypeError without ?.\n",[15,139,140,141,144,145,148,149,151],{},"Instead of ",[19,142,143],{},"user && user.account && user.account.balance",", you write ",[19,146,147],{},"user?.account?.balance",".\nThe expression stops at the first nullish link and yields ",[19,150,69],{},".",[10,153,155],{"id":154},"three-forms-of-optional-chaining","Three forms of optional chaining",[15,157,158,160],{},[19,159,29],{}," works for property access, dynamic (bracket) access, and method calls.",[75,162,164],{"className":77,"code":163,"language":79,"meta":80,"style":80},"obj?.prop          \u002F\u002F property access\nobj?.[key]         \u002F\u002F dynamic\u002Fcomputed access\nobj?.method?.()    \u002F\u002F method call — only calls if method exists\narr?.[0]           \u002F\u002F array element\n",[19,165,166,174,182,197],{"__ignoreMap":80},[84,167,168,171],{"class":86,"line":87},[84,169,170],{"class":101},"obj?.prop          ",[84,172,173],{"class":118},"\u002F\u002F property access\n",[84,175,176,179],{"class":86,"line":112},[84,177,178],{"class":101},"obj?.[key]         ",[84,180,181],{"class":118},"\u002F\u002F dynamic\u002Fcomputed access\n",[84,183,184,187,191,194],{"class":86,"line":122},[84,185,186],{"class":101},"obj?.",[84,188,190],{"class":189},"sScJk","method",[84,192,193],{"class":101},"?.()    ",[84,195,196],{"class":118},"\u002F\u002F method call — only calls if method exists\n",[84,198,199,202,205,208],{"class":86,"line":131},[84,200,201],{"class":101},"arr?.[",[84,203,204],{"class":94},"0",[84,206,207],{"class":101},"]           ",[84,209,210],{"class":118},"\u002F\u002F array element\n",[15,212,213,214,217,218,220],{},"The method-call form is especially handy: ",[19,215,216],{},"obj.method?.()"," calls ",[19,219,190],{}," only if it exists,\navoiding \"x is not a function\" errors when an optional callback may be absent.",[10,222,224],{"id":223},"short-circuiting","Short-circuiting",[15,226,227,228,230,231,234],{},"When ",[19,229,29],{}," short-circuits, it ",[24,232,233],{},"abandons the entire rest of the chain"," — subsequent accesses and\ncalls don't run at all.",[75,236,238],{"className":77,"code":237,"language":79,"meta":80,"style":80},"const result = a?.b.c.d\n\u002F\u002F if `a` is null\u002Fundefined, b, c, AND d are all skipped -> result is undefined\n",[19,239,240,252],{"__ignoreMap":80},[84,241,242,244,247,249],{"class":86,"line":87},[84,243,91],{"class":90},[84,245,246],{"class":94}," result",[84,248,98],{"class":90},[84,250,251],{"class":101}," a?.b.c.d\n",[84,253,254],{"class":86,"line":112},[84,255,256],{"class":118},"\u002F\u002F if `a` is null\u002Fundefined, b, c, AND d are all skipped -> result is undefined\n",[15,258,259,260,262,263,265],{},"This is important: you only need ",[19,261,29],{}," at the link that might be nullish, not on every step,\nbecause short-circuiting protects the rest. That said, putting ",[19,264,29],{}," at each genuinely-optional\nlink documents your assumptions clearly.",[10,267,269],{"id":268},"nullish-coalescing-basics","Nullish coalescing basics",[15,271,54,272,276,277,283],{},[24,273,274],{},[19,275,37],{}," operator returns its right-hand side only when the left is ",[24,278,279,66,281],{},[19,280,65],{},[19,282,69],{},". It's the safe way to provide defaults.",[75,285,287],{"className":77,"code":286,"language":79,"meta":80,"style":80},"const port = config.port ?? 8080      \u002F\u002F use 8080 only if port is null\u002Fundefined\nconst name = user.name ?? 'Anonymous'\n",[19,288,289,309],{"__ignoreMap":80},[84,290,291,293,296,298,301,303,306],{"class":86,"line":87},[84,292,91],{"class":90},[84,294,295],{"class":94}," port",[84,297,98],{"class":90},[84,299,300],{"class":101}," config.port ",[84,302,37],{"class":90},[84,304,305],{"class":94}," 8080",[84,307,308],{"class":118},"      \u002F\u002F use 8080 only if port is null\u002Fundefined\n",[84,310,311,313,316,318,321,323],{"class":86,"line":112},[84,312,91],{"class":90},[84,314,315],{"class":94}," name",[84,317,98],{"class":90},[84,319,320],{"class":101}," user.name ",[84,322,37],{"class":90},[84,324,325],{"class":105}," 'Anonymous'\n",[15,327,328],{},"It pairs naturally with optional chaining to access-and-default in one expression:",[75,330,332],{"className":77,"code":331,"language":79,"meta":80,"style":80},"const city = user?.address?.city ?? 'Unknown'   \u002F\u002F safe access + fallback\n",[19,333,334],{"__ignoreMap":80},[84,335,336,338,341,343,346,348,351],{"class":86,"line":87},[84,337,91],{"class":90},[84,339,340],{"class":94}," city",[84,342,98],{"class":90},[84,344,345],{"class":101}," user?.address?.city ",[84,347,37],{"class":90},[84,349,350],{"class":105}," 'Unknown'",[84,352,353],{"class":118},"   \u002F\u002F safe access + fallback\n",[10,355,357,358,360,361,363],{"id":356},"the-crucial-vs-distinction","The crucial ",[19,359,37],{}," vs ",[19,362,41],{}," distinction",[15,365,366,367,369,370,372,373,376,377,379,380,379,383,386,387,390,391,394],{},"This is the heart of why ",[19,368,37],{}," exists. The old ",[19,371,41],{}," operator returns the right side for ",[24,374,375],{},"any\nfalsy"," value — including ",[19,378,204],{},", ",[19,381,382],{},"''",[19,384,385],{},"false",", and ",[19,388,389],{},"NaN"," — which causes bugs when those are\n",[44,392,393],{},"valid"," values.",[75,396,398],{"className":77,"code":397,"language":79,"meta":80,"style":80},"const count = data.count || 10    \u002F\u002F if count is 0, you get 10!\nconst count2 = data.count ?? 10   \u002F\u002F 0 is kept; only null\u002Fundefined -> 10\n\nconst label = input.text || 'N\u002FA' \u002F\u002F empty string becomes 'N\u002FA'\nconst label2 = input.text ?? 'N\u002FA' \u002F\u002F '' is a valid value, kept\n",[19,399,400,420,438,444,464],{"__ignoreMap":80},[84,401,402,404,407,409,412,414,417],{"class":86,"line":87},[84,403,91],{"class":90},[84,405,406],{"class":94}," count",[84,408,98],{"class":90},[84,410,411],{"class":101}," data.count ",[84,413,41],{"class":90},[84,415,416],{"class":94}," 10",[84,418,419],{"class":118},"    \u002F\u002F if count is 0, you get 10!\n",[84,421,422,424,427,429,431,433,435],{"class":86,"line":112},[84,423,91],{"class":90},[84,425,426],{"class":94}," count2",[84,428,98],{"class":90},[84,430,411],{"class":101},[84,432,37],{"class":90},[84,434,416],{"class":94},[84,436,437],{"class":118},"   \u002F\u002F 0 is kept; only null\u002Fundefined -> 10\n",[84,439,440],{"class":86,"line":122},[84,441,443],{"emptyLinePlaceholder":442},true,"\n",[84,445,446,448,451,453,456,458,461],{"class":86,"line":131},[84,447,91],{"class":90},[84,449,450],{"class":94}," label",[84,452,98],{"class":90},[84,454,455],{"class":101}," input.text ",[84,457,41],{"class":90},[84,459,460],{"class":105}," 'N\u002FA'",[84,462,463],{"class":118}," \u002F\u002F empty string becomes 'N\u002FA'\n",[84,465,467,469,472,474,476,478,480],{"class":86,"line":466},5,[84,468,91],{"class":90},[84,470,471],{"class":94}," label2",[84,473,98],{"class":90},[84,475,455],{"class":101},[84,477,37],{"class":90},[84,479,460],{"class":105},[84,481,482],{"class":118}," \u002F\u002F '' is a valid value, kept\n",[15,484,485,486,490,491,379,493,495,496,498,499,501],{},"Rule of thumb: use ",[24,487,488],{},[19,489,37],{}," when ",[19,492,204],{},[19,494,382],{},", or ",[19,497,385],{}," are legitimate values you want to keep,\nand ",[19,500,41],{}," only when you genuinely want any falsy value to trigger the fallback.",[10,503,505],{"id":504},"logical-assignment-operators","Logical assignment operators",[15,507,508,509,512,513,379,516,386,519,522],{},"ES2021 added ",[24,510,511],{},"logical assignment"," operators that combine a logical operator with assignment:\n",[19,514,515],{},"??=",[19,517,518],{},"||=",[19,520,521],{},"&&=",". They assign only when the condition holds.",[75,524,526],{"className":77,"code":525,"language":79,"meta":80,"style":80},"config.timeout ??= 3000     \u002F\u002F assign 3000 only if timeout is null\u002Fundefined\noptions.list ||= []         \u002F\u002F assign [] only if list is falsy\nuser.active &&= validate()  \u002F\u002F assign validate() only if active is truthy\n",[19,527,528,541,554],{"__ignoreMap":80},[84,529,530,533,535,538],{"class":86,"line":87},[84,531,532],{"class":101},"config.timeout ",[84,534,515],{"class":90},[84,536,537],{"class":94}," 3000",[84,539,540],{"class":118},"     \u002F\u002F assign 3000 only if timeout is null\u002Fundefined\n",[84,542,543,546,548,551],{"class":86,"line":112},[84,544,545],{"class":101},"options.list ",[84,547,518],{"class":90},[84,549,550],{"class":101}," []         ",[84,552,553],{"class":118},"\u002F\u002F assign [] only if list is falsy\n",[84,555,556,559,561,564,567],{"class":86,"line":122},[84,557,558],{"class":101},"user.active ",[84,560,521],{"class":90},[84,562,563],{"class":189}," validate",[84,565,566],{"class":101},"()  ",[84,568,569],{"class":118},"\u002F\u002F assign validate() only if active is truthy\n",[15,571,572,574,575,577,578,577,580,582,583,586],{},[19,573,515],{}," is the safest for defaults — it sets a value only when the property is genuinely missing,\npreserving ",[19,576,204],{},"\u002F",[19,579,382],{},[19,581,385],{},". These operators also ",[24,584,585],{},"short-circuit",": the right side isn't\nevaluated when the assignment is skipped.",[10,588,590,591,31,593,595],{"id":589},"combining-and-with-precedence","Combining ",[19,592,29],{},[19,594,37],{}," with precedence",[15,597,598,31,600,602,603,606,607,609,610,612,613,616],{},[19,599,29],{},[19,601,37],{}," work together, but there's a syntax rule: you ",[24,604,605],{},"cannot"," directly mix ",[19,608,37],{}," with\n",[19,611,41],{}," or ",[19,614,615],{},"&&"," without parentheses, because their precedence is intentionally ambiguous.",[75,618,620],{"className":77,"code":619,"language":79,"meta":80,"style":80},"const x = a ?? b || c     \u002F\u002F SyntaxError\nconst y = (a ?? b) || c   \u002F\u002F explicit grouping required\nconst z = a ?? (b || c)   \u002F\u002F also fine\n",[19,621,622,647,672],{"__ignoreMap":80},[84,623,624,626,629,631,634,636,639,641,644],{"class":86,"line":87},[84,625,91],{"class":90},[84,627,628],{"class":94}," x",[84,630,98],{"class":90},[84,632,633],{"class":101}," a ",[84,635,37],{"class":90},[84,637,638],{"class":101}," b ",[84,640,41],{"class":90},[84,642,643],{"class":101}," c     ",[84,645,646],{"class":118},"\u002F\u002F SyntaxError\n",[84,648,649,651,654,656,659,661,664,666,669],{"class":86,"line":112},[84,650,91],{"class":90},[84,652,653],{"class":94}," y",[84,655,98],{"class":90},[84,657,658],{"class":101}," (a ",[84,660,37],{"class":90},[84,662,663],{"class":101}," b) ",[84,665,41],{"class":90},[84,667,668],{"class":101}," c   ",[84,670,671],{"class":118},"\u002F\u002F explicit grouping required\n",[84,673,674,676,679,681,683,685,688,690,693],{"class":86,"line":122},[84,675,91],{"class":90},[84,677,678],{"class":94}," z",[84,680,98],{"class":90},[84,682,633],{"class":101},[84,684,37],{"class":90},[84,686,687],{"class":101}," (b ",[84,689,41],{"class":90},[84,691,692],{"class":101}," c)   ",[84,694,695],{"class":118},"\u002F\u002F also fine\n",[15,697,698],{},"This forced clarity prevents subtle bugs from mixed-operator expressions.",[10,700,702],{"id":701},"dont-over-use-optional-chaining","Don't over-use optional chaining",[15,704,705,706,708,709,712,713,715,716,718],{},"The biggest pitfall is reaching for ",[19,707,29],{}," everywhere, which ",[24,710,711],{},"hides bugs",". If a value should\nalways exist, ",[19,714,29],{}," silently turns a programming error into a quiet ",[19,717,69],{}," that surfaces as\na confusing failure later.",[75,720,722],{"className":77,"code":721,"language":79,"meta":80,"style":80},"\u002F\u002F if `user` should always exist, this masks the real bug\nconst name = user?.name\n\n\u002F\u002F let it throw loudly so you catch the actual problem\nconst name = user.name\n",[19,723,724,729,740,744,749],{"__ignoreMap":80},[84,725,726],{"class":86,"line":87},[84,727,728],{"class":118},"\u002F\u002F if `user` should always exist, this masks the real bug\n",[84,730,731,733,735,737],{"class":86,"line":112},[84,732,91],{"class":90},[84,734,315],{"class":94},[84,736,98],{"class":90},[84,738,739],{"class":101}," user?.name\n",[84,741,742],{"class":86,"line":122},[84,743,443],{"emptyLinePlaceholder":442},[84,745,746],{"class":86,"line":131},[84,747,748],{"class":118},"\u002F\u002F let it throw loudly so you catch the actual problem\n",[84,750,751,753,755,757],{"class":86,"line":466},[84,752,91],{"class":90},[84,754,315],{"class":94},[84,756,98],{"class":90},[84,758,759],{"class":101}," user.name\n",[15,761,762,763,765,766,769],{},"Use ",[19,764,29],{}," only where a value is ",[24,767,768],{},"legitimately optional"," (data that may or may not be present),\nnot as a blanket shield over code you haven't reasoned about. Over-chaining makes failures\nharder to diagnose.",[10,771,773],{"id":772},"optional-chaining-is-for-reading-not-writing","Optional chaining is for reading, not writing",[15,775,776,777,779],{},"You can't use ",[19,778,29],{}," on the left side of an assignment — it's a read-only access operator.",[75,781,783],{"className":77,"code":782,"language":79,"meta":80,"style":80},"obj?.prop = 5    \u002F\u002F SyntaxError — invalid assignment target\nif (obj) obj.prop = 5   \u002F\u002F guard the write explicitly\n",[19,784,785,799],{"__ignoreMap":80},[84,786,787,790,793,796],{"class":86,"line":87},[84,788,789],{"class":101},"obj?.prop ",[84,791,792],{"class":90},"=",[84,794,795],{"class":94}," 5",[84,797,798],{"class":118},"    \u002F\u002F SyntaxError — invalid assignment target\n",[84,800,801,804,807,809,811],{"class":86,"line":112},[84,802,803],{"class":90},"if",[84,805,806],{"class":101}," (obj) obj.prop ",[84,808,792],{"class":90},[84,810,795],{"class":94},[84,812,813],{"class":118},"   \u002F\u002F guard the write explicitly\n",[15,815,816,817,577,819,821],{},"For conditional writes, guard with a normal check or use ",[19,818,515],{},[19,820,518],{}," on the property.",[10,823,825],{"id":824},"key-takeaways","Key takeaways",[827,828,829,844,859,881,894,904],"ul",{},[830,831,832,836,837,577,839,841,842,73],"li",{},[24,833,834],{},[19,835,29],{}," accesses a property\u002Fmethod only if the preceding value isn't ",[19,838,65],{},[19,840,69],{},",\nshort-circuiting to ",[19,843,69],{},[830,845,846,847,850,851,854,855,858],{},"It comes in property (",[19,848,849],{},"?.prop","), dynamic (",[19,852,853],{},"?.[key]","), and call (",[19,856,857],{},"?.()",") forms, and abandons\nthe rest of the chain on a nullish link.",[830,860,861,865,866,577,868,870,871,873,874,876,877,386,879,151],{},[24,862,863],{},[19,864,37],{}," returns the fallback only for ",[19,867,65],{},[19,869,69],{}," — unlike ",[19,872,41],{},", it preserves ",[19,875,204],{},",\n",[19,878,382],{},[19,880,385],{},[830,882,883,884,379,886,379,888,890,891,893],{},"Logical assignment (",[19,885,515],{},[19,887,518],{},[19,889,521],{},") assigns conditionally and short-circuits; ",[19,892,515],{}," is\nthe safe default-setter.",[830,895,896,897,899,900,577,902,151],{},"You must parenthesize when mixing ",[19,898,37],{}," with ",[19,901,41],{},[19,903,615],{},[830,905,906,907,909],{},"Don't over-use ",[19,908,29],{}," — it can mask real bugs; reserve it for genuinely optional values, and\nremember it can't be an assignment target.",[15,911,912],{},"Optional chaining and nullish coalescing make defensive code clean and intention-revealing — as\nlong as you respect the falsy-vs-nullish distinction and don't paper over errors that should\nsurface.",[914,915,916],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":80,"searchDepth":112,"depth":112,"links":918},[919,920,921,922,923,924,926,927,929,930,931],{"id":12,"depth":112,"text":13},{"id":50,"depth":112,"text":51},{"id":154,"depth":112,"text":155},{"id":223,"depth":112,"text":224},{"id":268,"depth":112,"text":269},{"id":356,"depth":112,"text":925},"The crucial ?? vs || distinction",{"id":504,"depth":112,"text":505},{"id":589,"depth":112,"text":928},"Combining ?. and ?? with precedence",{"id":701,"depth":112,"text":702},{"id":772,"depth":112,"text":773},{"id":824,"depth":112,"text":825},"Learn optional chaining (?.) and nullish coalescing (??) in JavaScript — safe property access, the falsy-vs-nullish distinction, logical assignment operators, and the pitfalls to avoid.","medium","md","JavaScript","javascript",{},"\u002Fblog\u002Fjavascript-optional-chaining-nullish-coalescing","\u002Fjavascript\u002Fmodern\u002Foptional-chaining-nullish",{"title":5,"description":932},"blog\u002Fjavascript-optional-chaining-nullish-coalescing","Optional Chaining & Nullish Coalescing","Modern JavaScript (ES6+)","modern","2026-06-18","Fe3Csx4rmbqRzs9FnuUS0yNqSlJOKeW1RFAi7hzY2uA",1781808673080]