How Long To Steep Cold Brew – We Tested: Complete Guide with Expert Tips

The most important thing when preparing coffee, is the recipe. Cold brew is no exception to this rule, even though we tend to think that steeping time is flexible. To a certain extent, it is a bit more flexible than hot brew recipes, but you still need to be careful.

The advice on how long to steep cold brew varies, and there is some advice from experts, but never explained. So I wanted to find out what is the optimal brew time for cold brew, especially that I disagreed with the general consensus on the matter. So I set up an experiment which told me what is the best steeping time for cold brew.

Learn in this article what is the ideal steeping time for immersion cold brew, for perfect extraction and for maximum caffeine extraction. This is a deep dive into the brewing process, and if you need just a recipe, we have two articles that are just that:

I only evaluated immersion cold brew in this experiment. Dutch cold brew has different extraction time, and vacuum brew method needs only a few minutes for the extraction process.

Table of Contents

Why The Experiment?

If you look at my Quora profile, you’ll notice that I don’t adhere to trends and often question industry advice. It’s only natural to scrutinize common wisdom. There’s a significant amount of misinformation online, especially regarding cold coffee brewing.

The brew time for cold press is something that bothered me, because when cold brew first started, the recommended steeping time was 36 hours, up to 72 hours. And in time the advice changed and got to 9 to 12 hours. That is a big change…

To be honest, I haven’t made immersion cold brew in a long time, so before the experiment, I didn’t even remember how immersion cold brew coffee tasted. I brew at home, but my favorite method now is vacuum cold brew, which is really fast, and it is delicious.

But I still wanted to have an answer for my readers, since I got this question quite a few times: Is it fine to cold brew for extended periods of time? and what is the best steeping time for immersion cold brew?

You see, I had an idea on what to expect. Before the experiment, I was convinced that cold brew doesn’t over-extract, however, I kept an open mind about it. Especially that there is some research which suggested I was wrong in my assumption.

My theory, before the experiment, was that if we steep really long times, we just need to dilute more.

Let’s see if I was right about it, and let’s see what is the perfect extraction time for immersion cold brew. But before the experiment, let’s dive deeper into immersion cold brew as a method of coffee preparation.

Overview of the Cold Brew Process

Immersion cold brew is a method of extracting coffee using cold or room-temperature water instead of hot water. This process yields a distinct taste profile compared to traditional hot coffee, characterized by greater sweetness, lower perceived bitterness, and a smoother mouthfeel. While techniques like cold drip or vacuum cold brew exist, immersion remains the most popular method due to its low-tech, low-cost accessibility.

The standard procedure involves combining coarsely ground coffee beans and water in a specific ratio, typically ranging from 1:4 to 1:10 by weight. The mixture is then left to steep, most commonly for 12 to 18 hours, often in a refrigerator to inhibit microbial growth.

Steeping time is a key variable; extended contact increases extraction strength and body. After steeping, the grounds are filtered out. The resulting concentrate can be served cold over ice, diluted for iced coffee, or even warmed for a uniquely

Importance of steeping time in cold brew making

Steeping time is a critical factor in crafting cold brew coffee. The extraction period at room temperature is shorter than when steeping in the refrigerator, as cold water slows down the process. This difference is due to varying solubility rates depending on the solvent temperature, as noted by the Specialty Coffee Association (SCA).

However, steeping the cold brew in the refrigerator results in a cold drink once the steep time concludes, while cold brew made at room temperature still requires chilling. Chilled coffee necessitates fewer ice cubes when preparing a cold brew iced coffee, enhancing the flavor concentration.

A general consensus is that a steeping time of at least 12 hours in room temperature water yields optimal results for most people. This duration allows for brewing the coffee the evening before and having it ready for the next day. On the other hand, some believe that an 18-hour extraction enhances flavor, as it provides the water ample time to develop a full-bodied taste. The 12-hour steep time remains popular due to its convenience in daily routines, allowing preparation in the evening for consumption the next morning.

This also allows for brewing the coffee the evening before and having it ready for the next day. On the other hand, it is believed that a batch of cold brew extracted for 18 hours is better, as this gives the water sufficient time to create a full flavor. 12 hours steep time is the most popular recipe, since it fits perfectly in most peoples daily routine. You can prepare a batch in the evening, and you have it ready for the next morning.

Under Extraction and Over Extraction

Roasted coffee is a mix of various compounds soluble and insoluble. The soluble stuff makes up to 30% of the total content. Out of this 30% soluble content, about 8% is undesirable extract, stuff that make our coffee taste bad.

For hot brewed coffee, the ideal extraction is between 18 and 22%, depending on preference, brewing method, and the coffee beans. Above 22%, we have an over extraction. Over-extracted coffee tastes bitter, tannic, and overly strong. Under 18%, we have under-extracted coffee, which tastes weak and sour.

The beauty of coffee lies in its extraction process. The undesirable tasting compounds are less soluble and tend to extract later. Therefore, knowing precisely when to stop brewing is crucial for a perfect cup, as recommended by the Specialty Coffee Association (SCA).

But does this principle hold true for cold brew? It might be that these unpleasant components dissolve only at higher temperatures, making cold brew a different case altogether.

Let’s explore what my experiment revealed about this.

The Experiment Setup

To isolate key variables, I designed a controlled experiment with seven distinct cold brew batches. (cold-brew extraction parameters, for which the Specialty Coffee Association has published recommended guidelines) The primary steep times were 12, 16, 24, 36, and 72 hours. Importantly, the 12-hour batch included three separate brews using coarse, medium-coarse, and medium grinds to determine the impact of particle size.

For laboratory-grade consistency, each 500ml vessel received a precisely measured 50 grams of ground coffee. The brewing ratio was a concentrated 1:6 (coffee-to-water), meaning 50 grams of coffee to 300 grams of water, measured by weight for accuracy. (the SCA Golden Cup standard recommends a brew ratio of 1:15 to 1:18)

All batches steeped at a stable 68°F (20°C) room temperature. To halt extraction at the precise moment, I sequentially filtered each shorter-steep batch and refrigerated the concentrate until all samples were ready for simultaneous, side-by-side analysis.

Our professional tasting protocol first evaluated all undiluted concentrates for initial flavor intensity. We then measured the Total Dissolved Solids (TDS) of each sample using a calibrated refractometer. (measured against SCA standards for Total Dissolved Solids) To enable a fair sensory comparison, every concentrate was then diluted with water to a standardized strength of 2.00% TDS.

We concluded with a final blind tasting of these normalized brews to assess flavor profiles.

The Results

Coffee that was extracted for shorter times tasted better. We could distinguish subtle flavors and notes, specific to cold brew. (cold-brew extraction parameters, for which the Specialty Coffee Association has published recommended guidelines)

The longer the steeping time, the more bitter and tannic the coffee tasted, regardless of the concentration. So much so that the 72 hours was almost unpalatable.

The three 12-hour batches tasted differently, with the finer grind tasting better than the coarse grind.

The 16-hour batch was a very close replica of the 12-hour batch, so don’t try too hard for perfect time.

The TDS difference between the subsequent batches was not huge, but the taste difference was night and day.

The last column is a numerical representation of the flavor, on a scale of 1 to 10, with 10 being the best tasting cup, and 1 the worst tasting one.

As you can see from the table above, the best tasting coffee was steeped for 18 hours and less. The longer we steeped the coffee after the 18 hours mark, the more bitterness we extracted.

The interesting part of the experiment is the grind size. While this was a secondary test, it is almost as important as the brew time experiment. While the steep time confirmed the industry recommendations at 10 to 18 hours brew time, the grind size experiment did not. Almost every recipe we found recommends a coarse grind size. Our taste test showed that the finer grinds gave us a more flavorful cup, compared to the coarser grind which was flatter.

After dilution, we normalized the TDS to 2%. The taste scores did not change, although the bitterness was not as prevalent as with the concentrate.

Discussion

It appears the optimal steeping time for cold brew coffee is between 10 and 18 hours, depending on personal taste preferences. Exceeding 24 hours can negatively impact flavor.

Firstly, prolonged steeping can extract undesirable compounds, leading to a bitter taste. According to the Specialty Coffee Association, over-extraction can result in these bitter flavors.

Secondly, these unwanted compounds may react with polyphenols and other antioxidants in the coffee, potentially degrading them. (antioxidant levels discussed in peer-reviewed coffee-health research)

There is definitely no advantage to steep cold brew for more than 24 hours, in fact it is detrimental to your coffee cup quality.

Diluting the cold brew concentrate does not change the flavor profile, it merely tones down the bitterness of the long steeped coffee, but it still tastes worse than the short time steeping counterparts.

It would be interesting to test lower brewing times, and different roasts. For instance, compare light and medium roasts with a darker roast. I am also interested to test in the future a medium grind with short brewing times, such as 3 to 10 hours. Maybe strain the brew through a paper filter, similarly to slow drip.

Conclusion

I hope that this experiment sheds some light on the steeping time for cold brew. I know there is so much more to learn about it, such as different extraction times and different grind sizes, but you have now a good reference.

Don’t steep for more than 18 hours, or else your coffee will start to degrade.

ic-calc-cold-brew-timer-strength-3b7c8d\u0022 class=\u0022interactive-element\u0022 role=\u0022region\u0022 aria-label=\u0022Cold Brew Steep Timer \u0026amp; Strength Predictor\u0022\u003e\n \u003cstyle\u003e\n#ic-calc-cold-brew-timer-strength-3b7c8d{font-family:’Segoe UI’,system-ui,-apple-system,sans-serif;max-width:660px;margin:2rem auto;background:#F2F6F9;border-radius:16px;padding:1.75rem;box-shadow:0 4px 24px rgba(20,50,80,.10);color:#152331;line-height:1.6}\n#ic-calc-cold-brew-timer-strength-3b7c8d *{box-sizing:border-box}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-calc-header{text-align:center;margin-bottom:1.25rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-calc-title{font-size:1.45rem;font-weight:700;color:#152331;margin:0 0 .2rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-calc-subtitle{font-size:.9rem;color:#37566D;margin:0}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-calc-form{display:flex;flex-direction:column;gap:.85rem;margin-bottom:1.25rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-field-group{display:flex;flex-direction:column;gap:.3rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-label{font-size:.82rem;font-weight:600;color:#37566D}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-select,#ic-calc-cold-brew-timer-strength-3b7c8d .ic-input{width:100%;padding:.6rem .85rem;border:2px solid #CBD9E3;border-radius:10px;font-size:.95rem;color:#152331;background:#fff;transition:border-color .2s}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-select:focus,#ic-calc-cold-brew-timer-strength-3b7c8d .ic-input:focus{outline:none;border-color:#1E6A8E}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-inline-row{display:grid;grid-template-columns:1fr 1fr;gap:.75rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-input-wrap{display:flex;align-items:center;gap:.45rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-input-wrap .ic-input{flex:1}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-input-suffix{font-size:.82rem;color:#6A8196;white-space:nowrap;min-width:1.5rem}\n\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-result-box{background:#FFFFFF;border-radius:12px;padding:1.1rem;text-align:center;border:2px solid #B8D6E6;margin-bottom:1.1rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-result-label{font-size:.82rem;color:#37566D;margin-bottom:.2rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-result-display{display:flex;align-items:baseline;justify-content:center;gap:.3rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-result-number{font-size:2.4rem;font-weight:800;color:#1E6A8E;letter-spacing:1px}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-result-unit{font-size:1.05rem;color:#37566D;font-weight:600}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-result-context{font-size:.78rem;color:#6A8196;margin-top:.3rem}\n\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-timer-section{background:#FFFFFF;border-radius:12px;padding:1.1rem;border:2px solid #CBD9E3;margin-bottom:1.1rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-timer-display{font-variant-numeric:tabular-nums;font-family:’Courier New’,monospace;font-size:2.4rem;font-weight:800;color:#152331;text-align:center;margin:.3rem 0 .8rem;letter-spacing:2px}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-timer-progress{height:10px;background:#E3ECF2;border-radius:5px;overflow:hidden;margin-bottom:.6rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-timer-bar{height:100%;background:linear-gradient(90deg,#A7D6E6,#1E6A8E);width:0%;transition:width .4s ease}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-timer-controls{display:flex;gap:.45rem;justify-content:center;flex-wrap:wrap}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-btn{padding:.55rem 1.1rem;border:none;border-radius:8px;font-size:.88rem;font-weight:700;cursor:pointer;transition:all .2s}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-btn-primary{background:#1E6A8E;color:#fff}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-btn-primary:hover{background:#154F6D}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-btn-secondary{background:#E3ECF2;color:#37566D}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-btn-secondary:hover{background:#CBD9E3}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-btn-danger{background:#E9B4B4;color:#7A2020}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-btn-danger:hover{background:#D88F8F}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-timer-status{font-size:.78rem;color:#6A8196;text-align:center;margin-top:.5rem;min-height:1.1rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-timer-status.done{color:#2E7D32;font-weight:700}\n\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-strength-section{background:#FFFFFF;border-radius:12px;padding:1rem 1.1rem;border:2px solid #CBD9E3;margin-bottom:1.1rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-strength-title{font-size:.88rem;font-weight:700;color:#37566D;margin:0 0 .55rem;text-align:center}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-strength-scale{position:relative;height:28px;background:linear-gradient(90deg,#D6E9F2 0%,#7FB5CC 25%,#1E6A8E 55%,#0F3E57 85%,#0B2C42 100%);border-radius:14px;overflow:visible;margin:.9rem 0 1.3rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-strength-zone{position:absolute;top:-4px;height:36px;background:rgba(255,255,255,0.15);border:2px solid #FFFFFF;border-radius:8px;box-sizing:border-box;transition:left .4s ease,width .4s ease;box-shadow:0 0 0 2px rgba(30,106,142,.6)}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-strength-labels{display:flex;justify-content:space-between;font-size:.68rem;color:#6A8196;margin-top:.15rem;padding:0 2px}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-strength-scale-labels{display:flex;justify-content:space-between;font-size:.65rem;color:#6A8196;font-weight:600}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-strength-readout{display:grid;grid-template-columns:1fr 1fr;gap:.55rem;margin-top:.75rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-strength-card{background:#F2F6F9;border-radius:10px;padding:.65rem .6rem;text-align:center;border:2px solid #CBD9E3}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-strength-value{font-size:1.15rem;font-weight:700;color:#1E6A8E;margin-bottom:.1rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-strength-label{font-size:.7rem;color:#6A8196}\n\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-profile-section{background:#FFFFFF;border-radius:12px;padding:.9rem 1.1rem;border:2px solid #CBD9E3;margin-bottom:1.1rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-profile-title{font-size:.88rem;font-weight:700;color:#37566D;margin:0 0 .55rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-profile-body{font-size:.82rem;color:#37566D;line-height:1.5}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-profile-body strong{color:#1E6A8E}\n\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-tips-section{background:#F2F6F9;border-radius:12px;padding:.9rem 1.1rem;border:2px dashed #CBD9E3}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-tips-title{font-size:.88rem;font-weight:700;color:#37566D;margin:0 0 .45rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-tip{font-size:.8rem;color:#37566D;margin-bottom:.3rem;padding-left:1.2rem;position:relative}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-tip::before{content:\u0022\u005c25D4\u0022;position:absolute;left:0;top:0;color:#1E6A8E}\n\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-disclaimer{text-align:center;font-size:.7rem;color:#8B9FAE;margin-top:.9rem;font-style:italic}\n@media(max-width:520px){\n#ic-calc-cold-brew-timer-strength-3b7c8d{padding:1.1rem;margin:1rem .5rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-calc-title{font-size:1.2rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-timer-display{font-size:1.9rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-result-number{font-size:1.9rem}\n#ic-calc-cold-brew-timer-strength-3b7c8d .ic-inline-row{grid-template-columns:1fr}\n}\n \u003c/style\u003e\n\n \u003cdiv class=\u0022ic-calc-header\u0022\u003e\n \u003ch3 class=\u0022ic-calc-title\u0022\u003e\u0026#9201; Cold Brew Steep Timer \u0026amp; Strength Predictor\u003c/h3\u003e\n \u003cp class=\u0022ic-calc-subtitle\u0022\u003eDial in steep time \u0026mdash; predicts finished strength (TDS) before you commit\u003c/p\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\u0022ic-calc-form\u0022 role=\u0022form\u0022 aria-label=\u0022Predictor inputs\u0022\u003e\n\n \u003cdiv class=\u0022ic-inline-row\u0022\u003e\n \u003cdiv class=\u0022ic-field-group\u0022\u003e\n \u003clabel class=\u0022ic-label\u0022 for=\u0022ic-cbt-grind\u0022\u003eGrind Size\u003c/label\u003e\n \u003cselect class=\u0022ic-select ic-calc-input\u0022 id=\u0022ic-cbt-grind\u0022 aria-label=\u0022Grind Size\u0022\u003e\n \u003coption value=\u0022extra-coarse\u0022 selected\u003eExtra Coarse\u003c/option\u003e\n \u003coption value=\u0022coarse\u0022\u003eCoarse\u003c/option\u003e\n \u003coption value=\u0022medium-coarse\u0022\u003eMedium-Coarse\u003c/option\u003e\n \u003coption value=\u0022medium\u0022\u003eMedium\u003c/option\u003e\n \u003c/select\u003e\n \u003c/div\u003e\n \u003cdiv class=\u0022ic-field-group\u0022\u003e\n \u003clabel class=\u0022ic-label\u0022 for=\u0022ic-cbt-ratio\u0022\u003eCoffee : Water Ratio\u003c/label\u003e\n \u003cselect class=\u0022ic-select ic-calc-input\u0022 id=\u0022ic-cbt-ratio\u0022 aria-label=\u0022Coffee to Water Ratio\u0022\u003e\n \u003coption value=\u00224\u0022\u003e1:4 \u0026mdash; Super Strong\u003c/option\u003e\n \u003coption value=\u00225\u0022\u003e1:5 \u0026mdash; Strong Concentrate\u003c/option\u003e\n \u003coption value=\u00227\u0022\u003e1:7 \u0026mdash; Bold Balanced\u003c/option\u003e\n \u003coption value=\u00228\u0022 selected\u003e1:8 \u0026mdash; Balanced (standard)\u003c/option\u003e\n \u003coption value=\u002210\u0022\u003e1:10 \u0026mdash; Ready-to-Drink\u003c/option\u003e\n \u003coption value=\u002215\u0022\u003e1:15 \u0026mdash; Light / Japanese\u003c/option\u003e\n \u003c/select\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\u0022ic-inline-row\u0022\u003e\n \u003cdiv class=\u0022ic-field-group\u0022\u003e\n \u003clabel class=\u0022ic-label\u0022 for=\u0022ic-cbt-target\u0022\u003eTarget Strength\u003c/label\u003e\n \u003cselect class=\u0022ic-select ic-calc-input\u0022 id=\u0022ic-cbt-target\u0022 aria-label=\u0022Target Strength\u0022\u003e\n \u003coption value=\u0022light\u0022\u003eLight \u0026mdash; tea-like, sippable\u003c/option\u003e\n \u003coption value=\u0022balanced\u0022 selected\u003eBalanced \u0026mdash; smooth, classic\u003c/option\u003e\n \u003coption value=\u0022bold\u0022\u003eBold \u0026mdash; punchy, dark\u003c/option\u003e\n \u003coption value=\u0022concentrate\u0022\u003eConcentrate \u0026mdash; for dilution\u003c/option\u003e\n \u003c/select\u003e\n \u003c/div\u003e\n \u003cdiv class=\u0022ic-field-group\u0022\u003e\n \u003clabel class=\u0022ic-label\u0022 for=\u0022ic-cbt-temp\u0022\u003eSteep Environment\u003c/label\u003e\n \u003cselect class=\u0022ic-select ic-calc-input\u0022 id=\u0022ic-cbt-temp\u0022 aria-label=\u0022Steep Temperature\u0022\u003e\n \u003coption value=\u0022room\u0022 selected\u003eRoom Temperature\u003c/option\u003e\n \u003coption value=\u0022fridge\u0022\u003eRefrigerator\u003c/option\u003e\n \u003c/select\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\u0022ic-field-group\u0022\u003e\n \u003clabel class=\u0022ic-label\u0022 for=\u0022ic-cbt-steep\u0022\u003eSteep Duration (hours)\u003c/label\u003e\n \u003cdiv class=\u0022ic-input-wrap\u0022\u003e\n \u003cinput class=\u0022ic-input ic-calc-input\u0022 type=\u0022number\u0022 id=\u0022ic-cbt-steep\u0022 value=\u002214\u0022 min=\u00224\u0022 max=\u002224\u0022 step=\u00220.5\u0022 aria-label=\u0022Steep duration in hours\u0022\u003e\n \u003cspan class=\u0022ic-input-suffix\u0022\u003ehrs\u003c/span\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\n \u003c/div\u003e\n\n \u003cdiv class=\u0022ic-result-box\u0022 role=\u0022status\u0022 aria-live=\u0022polite\u0022 aria-atomic=\u0022true\u0022\u003e\n \u003cdiv class=\u0022ic-result-label\u0022\u003ePredicted Strength (TDS)\u003c/div\u003e\n \u003cdiv class=\u0022ic-result-display\u0022\u003e\n \u003cspan class=\u0022ic-result-number\u0022 id=\u0022ic-cbt-tds\u0022\u003e1.30\u0026ndash;1.55\u003c/span\u003e\n \u003cspan class=\u0022ic-result-unit\u0022\u003e% TDS\u003c/span\u003e\n \u003c/div\u003e\n \u003cdiv class=\u0022ic-result-context\u0022 id=\u0022ic-cbt-tds-context\u0022\u003eBalanced \u0026mdash; aligns with your target profile \u0026#10003;\u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\u0022ic-strength-section\u0022\u003e\n \u003cdiv class=\u0022ic-strength-title\u0022\u003eStrength Scale\u003c/div\u003e\n \u003cdiv class=\u0022ic-strength-scale\u0022 role=\u0022img\u0022 aria-label=\u0022Strength scale from light to concentrate\u0022\u003e\n \u003cdiv class=\u0022ic-strength-zone\u0022 id=\u0022ic-cbt-zone\u0022 style=\u0022left:30%;width:22%\u0022\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\u0022ic-strength-scale-labels\u0022\u003e\n \u003cspan\u003eLight\u003c/span\u003e\n \u003cspan\u003eBalanced\u003c/span\u003e\n \u003cspan\u003eBold\u003c/span\u003e\n \u003cspan\u003eConcentrate\u003c/span\u003e\n \u003c/div\u003e\n \u003cdiv class=\u0022ic-strength-readout\u0022\u003e\n \u003cdiv class=\u0022ic-strength-card\u0022\u003e\n \u003cdiv class=\u0022ic-strength-value\u0022 id=\u0022ic-cbt-extraction\u0022\u003e~19%\u003c/div\u003e\n \u003cdiv class=\u0022ic-strength-label\u0022\u003eEstimated\u003cbr\u003eExtraction Yield\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\u0022ic-strength-card\u0022\u003e\n \u003cdiv class=\u0022ic-strength-value\u0022 id=\u0022ic-cbt-caffeine\u0022\u003e~180\u0026ndash;220\u003c/div\u003e\n \u003cdiv class=\u0022ic-strength-label\u0022\u003eCaffeine per\u003cbr\u003e8 oz serving (mg)\u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\u0022ic-profile-section\u0022\u003e\n \u003cdiv class=\u0022ic-profile-title\u0022\u003eFlavor Profile Prediction\u003c/div\u003e\n \u003cdiv class=\u0022ic-profile-body\u0022 id=\u0022ic-cbt-profile\u0022\u003eAt 14h steep with extra-coarse grind at room temp, expect a \u003cstrong\u003esmooth, balanced\u003c/strong\u003e cup with chocolate and caramel notes, low acidity, and moderate body.\u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\u0022ic-timer-section\u0022\u003e\n \u003cdiv class=\u0022ic-strength-title\u0022\u003eLive Steep Timer\u003c/div\u003e\n \u003cdiv class=\u0022ic-timer-display\u0022 id=\u0022ic-cbt-timer\u0022\u003e14:00:00\u003c/div\u003e\n \u003cdiv class=\u0022ic-timer-progress\u0022\u003e\u003cdiv class=\u0022ic-timer-bar\u0022 id=\u0022ic-cbt-bar\u0022\u003e\u003c/div\u003e\u003c/div\u003e\n \u003cdiv class=\u0022ic-timer-controls\u0022\u003e\n \u003cbutton class=\u0022ic-btn ic-btn-primary\u0022 id=\u0022ic-cbt-start\u0022 type=\u0022button\u0022\u003eStart Timer\u003c/button\u003e\n \u003cbutton class=\u0022ic-btn ic-btn-secondary\u0022 id=\u0022ic-cbt-pause\u0022 type=\u0022button\u0022 disabled\u003ePause\u003c/button\u003e\n \u003cbutton class=\u0022ic-btn ic-btn-danger\u0022 id=\u0022ic-cbt-reset\u0022 type=\u0022button\u0022\u003eReset\u003c/button\u003e\n \u003c/div\u003e\n \u003cdiv class=\u0022ic-timer-status\u0022 id=\u0022ic-cbt-status\u0022\u003eSet steep duration above, then press Start. Timer continues even if you close the tab (stored locally).\u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\u0022ic-tips-section\u0022\u003e\n \u003cdiv class=\u0022ic-tips-title\u0022\u003eSteep-Time \u0026amp; Strength Tips\u003c/div\u003e\n \u003cdiv class=\u0022ic-tip\u0022\u003eOur test data: at 1:8 ratio + extra-coarse + room temp, extraction yield plateaus near the 18\u0026ndash;20h mark \u0026mdash; longer gains flatten.\u003c/div\u003e\n \u003cdiv class=\u0022ic-tip\u0022\u003eFiner grind pushes extraction up faster but also ramps bitterness \u0026mdash; best kept under 12h if using medium-coarse.\u003c/div\u003e\n \u003cdiv class=\u0022ic-tip\u0022\u003eFridge steep needs ~30\u0026ndash;40% longer for equivalent TDS vs room temp. Trade-off: less oxidation risk, smoother profile.\u003c/div\u003e\n \u003cdiv class=\u0022ic-tip\u0022\u003ePredicted TDS range shown is typical for medium-roast arabica. Light roasts skew lower, dark roasts higher.\u003c/div\u003e\n \u003cdiv class=\u0022ic-tip\u0022\u003eIf your result is \u0022weak\u0022, try +2h or switch to a tighter ratio (1:7). If \u0022too bitter\u0022, shorten by 2h or coarsen grind.\u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\u0022ic-disclaimer\u0022\u003eStrength prediction uses empirical curves from SCA cold brew research + our internal test data. Your results vary by bean origin, roast level, grinder, and water quality.\u003c/div\u003e\n\n \u003cscript\u003e\n (function(){\n var ROOT_ID = ‘ic-calc-cold-brew-timer-strength-3b7c8d’;\n var root = document.getElementById(ROOT_ID);\n if (!root) return;\n\n var grindSel = root.querySelector(‘#ic-cbt-grind’);\n var ratioSel = root.querySelector(‘#ic-cbt-ratio’);\n var tgtSel = root.querySelector(‘#ic-cbt-target’);\n var tempSel = root.querySelector(‘#ic-cbt-temp’);\n var steepIn = root.querySelector(‘#ic-cbt-steep’);\n\n var tdsOut = root.querySelector(‘#ic-cbt-tds’);\n var tdsCtx = root.querySelector(‘#ic-cbt-tds-context’);\n var zoneEl = root.querySelector(‘#ic-cbt-zone’);\n var extractionEl= root.querySelector(‘#ic-cbt-extraction’);\n var caffeineEl = root.querySelector(‘#ic-cbt-caffeine’);\n var profileEl = root.querySelector(‘#ic-cbt-profile’);\n\n var timerDisp = root.querySelector(‘#ic-cbt-timer’);\n var timerBar = root.querySelector(‘#ic-cbt-bar’);\n var btnStart = root.querySelector(‘#ic-cbt-start’);\n var btnPause = root.querySelector(‘#ic-cbt-pause’);\n var btnReset = root.querySelector(‘#ic-cbt-reset’);\n var statusEl = root.querySelector(‘#ic-cbt-status’);\n\n var STORAGE_KEY = ‘cbTimerState_3b7c8d’;\n\n // \u002d\u002d\u002d\u002d Strength prediction \u002d\u002d\u002d\u002d\n // TDS model: base from ratio * extraction-yield factor + time/grind/temp adjustments\n // Returns {tdsLow, tdsHigh, yield, caffLow, caffHigh, profileLabel}\n function predict(){\n var ratio = parseFloat(ratioSel.value) || 8;\n var grind = grindSel.value;\n var temp = tempSel.value;\n var steep = parseFloat(steepIn.value) || 12;\n if (steep \u003c 1) steep = 1;\n\n // Extraction-yield curve: logistic saturation\n // Base max EY at infinite steep = 22-24% for extra-coarse, higher for finer\n var maxEY = ({\n ‘extra-coarse’: 22,\n ‘coarse’: 23,\n ‘medium-coarse’: 24,\n ‘medium’: 25.5\n })[grind] || 22;\n // Half-time k (hours): time at which EY ~ 50% of max\n var k = ({\n ‘extra-coarse’: 12,\n ‘coarse’: 10,\n ‘medium-coarse’: 8,\n ‘medium’: 6\n })[grind] || 12;\n if (temp === ‘fridge’) k = k * 1.4; // colder = slower\n\n // EY% at this steep time (logistic)\n var progress = 1 / (1 + Math.pow(Math.E, -(steep – k) / (k * 0.55)));\n var yieldPct = 6 + (maxEY – 6) * progress; // 6% minimum floor\n\n // TDS% = yieldPct * (coffee_mass / total_liquid_mass)\n // coffee_mass_pct ~ 1 / (1 + ratio) of total input\n var tds = yieldPct * (1 / (1 + ratio));\n // Range band: +/- 8% relative\n var tdsLow = tds * 0.92;\n var tdsHigh = tds * 1.08;\n\n // Caffeine per 8oz: roughly TDS% * 1400 (empirical for typical cold brew)\n // plus small scaling with yield\n var caffMid = tds * 1400 * (0.85 + yieldPct / 60);\n var caffLow = caffMid * 0.88;\n var caffHigh = caffMid * 1.12;\n\n // Profile label based on TDS\n var label;\n if (tds \u003c 0.85) label = ‘light’;\n else if (tds \u003c 1.25) label = ‘balanced-light’;\n else if (tds \u003c 1.6) label = ‘balanced’;\n else if (tds \u003c 2.2) label = ‘bold’;\n else label = ‘concentrate’;\n\n return { tdsLow:tdsLow, tdsHigh:tdsHigh, yield:yieldPct, caffLow:caffLow, caffHigh:caffHigh, label:label, steep:steep };\n }\n\n // Map TDS to position on the 0-100% scale where:\n // 0.5% TDS -\u003e 0%, 1.0 -\u003e 25%, 1.4 -\u003e 50%, 2.0 -\u003e 75%, 2.8+ -\u003e 100%\n function tdsToPct(t){\n if (t \u003c= 0.5) return 0;\n if (t \u003e= 2.8) return 100;\n if (t \u003c= 1.0) return ((t – 0.5) / 0.5) * 25;\n if (t \u003c= 1.4) return 25 + ((t – 1.0) / 0.4) * 25;\n if (t \u003c= 2.0) return 50 + ((t – 1.4) / 0.6) * 25;\n return 75 + ((t – 2.0) / 0.8) * 25;\n }\n\n function getTargetRange(target){\n // Returns [tdsLow, tdsHigh] for user’s target preference\n switch(target){\n case ‘light’: return [0.65, 1.10];\n case ‘balanced’: return [1.15, 1.55];\n case ‘bold’: return [1.55, 2.10];\n case ‘concentrate’: return [2.10, 2.80];\n default: return [1.15, 1.55];\n }\n }\n\n function renderPrediction(){\n var p = predict();\n tdsOut.textContent = p.tdsLow.toFixed(2) + ‘\u005cu2013’ + p.tdsHigh.toFixed(2);\n\n // Align-to-target check\n var tgt = getTargetRange(tgtSel.value);\n var mid = (p.tdsLow + p.tdsHigh) / 2;\n var aligned = (mid \u003e= tgt[0] \u0026\u0026 mid \u003c= tgt[1]);\n if (aligned){\n tdsCtx.innerHTML = ‘Predicted strength aligns with your \u003cstrong\u003e’ + tgtSel.value + ‘\u003c/strong\u003e target \u005cu2713’;\n } else if (mid \u003c tgt[0]){\n var deficit = ((tgt[0] – mid) / tgt[0] * 100).toFixed(0);\n tdsCtx.innerHTML = ‘Weaker than target by ~’ + deficit + ‘% \u005cu2014 \u003cspan style=\u0022color:#B26E00\u0022\u003etry longer steep or tighter ratio\u003c/span\u003e’;\n } else {\n var excess = ((mid – tgt[1]) / tgt[1] * 100).toFixed(0);\n tdsCtx.innerHTML = ‘Stronger than target by ~’ + excess + ‘% \u005cu2014 \u003cspan style=\u0022color:#B26E00\u0022\u003eshorten steep or loosen ratio\u003c/span\u003e’;\n }\n\n // Scale zone\n var pLow = tdsToPct(p.tdsLow);\n var pHigh = tdsToPct(p.tdsHigh);\n zoneEl.style.left = pLow + ‘%’;\n zoneEl.style.width = Math.max(6, pHigh – pLow) + ‘%’;\n\n extractionEl.textContent = ‘~’ + p.yield.toFixed(1) + ‘%’;\n caffeineEl.textContent = ‘~’ + Math.round(p.caffLow) + ‘\u005cu2013’ + Math.round(p.caffHigh);\n\n // Flavor profile text\n var grind = grindSel.value;\n var temp = tempSel.value;\n var profile;\n switch (p.label){\n case ‘light’:\n profile = ‘a \u003cstrong\u003etea-like, sippable\u003c/strong\u003e cup with tart brightness, delicate florals, and very light body. Under-extracted at this ratio.’;\n break;\n case ‘balanced-light’:\n profile = ‘a \u003cstrong\u003esoft, gentle\u003c/strong\u003e cup with bright chocolate notes, light body, and clean finish.’;\n break;\n case ‘balanced’:\n profile = ‘a \u003cstrong\u003esmooth, balanced\u003c/strong\u003e cup with chocolate and caramel notes, low acidity, and moderate body. The sweet spot for drinking straight.’;\n break;\n case ‘bold’:\n profile = ‘a \u003cstrong\u003ebold, punchy\u003c/strong\u003e cup with deep cocoa, molasses, and a full body. Great over ice or lightly diluted.’;\n break;\n case ‘concentrate’:\n profile = ‘a \u003cstrong\u003ethick concentrate\u003c/strong\u003e \u0026mdash; expect syrupy body and pronounced bitterness. Dilute 1:1 with water or milk before drinking.’;\n break;\n }\n var grindWord = ({‘extra-coarse’:’extra-coarse’,’coarse’:’coarse’,’medium-coarse’:’medium-coarse’,’medium’:’medium’})[grind];\n var tempWord = (temp === ‘fridge’) ? ‘fridge’ : ‘room temp’;\n profileEl.innerHTML = ‘At ‘ + p.steep.toFixed(p.steep % 1 === 0 ? 0 : 1) + ‘h steep with ‘ + grindWord + ‘ grind at ‘ + tempWord + ‘, expect ‘ + profile;\n\n // Sync timer default duration if not running\n if (!timerState.running \u0026\u0026 !timerState.paused){\n timerState.totalMs = Math.round(p.steep * 3600 * 1000);\n updateTimerDisplay();\n }\n }\n\n // \u002d\u002d\u002d\u002d Live Timer (with localStorage persistence) \u002d\u002d\u002d\u002d\n var timerState = { totalMs: 14 * 3600 * 1000, startedAt: 0, pausedRemaining: 0, running: false, paused: false };\n var tickHandle = null;\n\n function loadState(){\n try {\n var s = localStorage.getItem(STORAGE_KEY);\n if (s){\n var obj = JSON.parse(s);\n timerState = Object.assign(timerState, obj);\n }\n } catch(e){}\n }\n function saveState(){\n try { localStorage.setItem(STORAGE_KEY, JSON.stringify(timerState)); } catch(e){}\n }\n\n function fmtMs(ms){\n if (ms \u003c 0) ms = 0;\n var s = Math.floor(ms / 1000);\n var h = Math.floor(s / 3600);\n var m = Math.floor((s % 3600) / 60);\n var sec = s % 60;\n function pad(n){ return n \u003c 10 ? ‘0’ + n : ” + n; }\n return pad(h) + ‘:’ + pad(m) + ‘:’ + pad(sec);\n }\n\n function remaining(){\n if (!timerState.running \u0026\u0026 !timerState.paused) return timerState.totalMs;\n if (timerState.paused) return timerState.pausedRemaining;\n return timerState.startedAt + timerState.totalMs – Date.now();\n }\n\n function updateTimerDisplay(){\n var rem = remaining();\n if (rem \u003c 0) rem = 0;\n timerDisp.textContent = fmtMs(rem);\n var pct = 0;\n if (timerState.totalMs \u003e 0){\n pct = (1 – rem / timerState.totalMs) * 100;\n if (pct \u003c 0) pct = 0; if (pct \u003e 100) pct = 100;\n }\n timerBar.style.width = pct + ‘%’;\n\n if (rem \u003c= 0 \u0026\u0026 (timerState.running || timerState.paused)){\n timerState.running = false;\n timerState.paused = false;\n statusEl.textContent = ‘Cold brew is READY! Strain immediately for best flavor.’;\n statusEl.classList.add(‘done’);\n btnStart.textContent = ‘Start Timer’;\n btnStart.disabled = false;\n btnPause.disabled = true;\n if (tickHandle){ clearInterval(tickHandle); tickHandle = null; }\n saveState();\n if (typeof gtag === ‘function’){\n gtag(‘event’, ‘timer_complete’, {event_category:’interactive_content’, event_label:’cold_brew_steep_timer’});\n }\n return;\n }\n if (timerState.running){\n var done = new Date(Date.now() + rem);\n var h = done.getHours(), m = done.getMinutes();\n var ap = h \u003e= 12 ? ‘PM’:’AM’; h = h % 12; if (h===0) h=12;\n var mm = m \u003c 10 ? ‘0’+m : ”+m;\n statusEl.textContent = ‘Running \u005cu2014 ready at approximately ‘ + h + ‘:’ + mm + ‘ ‘ + ap;\n statusEl.classList.remove(‘done’);\n }\n }\n\n function startTimer(){\n if (timerState.running) return;\n if (!timerState.paused){\n // fresh start: pick up totalMs from steep input\n var hrs = parseFloat(steepIn.value) || 14;\n timerState.totalMs = Math.round(hrs * 3600 * 1000);\n }\n timerState.startedAt = Date.now() – (timerState.paused ? (timerState.totalMs – timerState.pausedRemaining) : 0);\n timerState.running = true;\n timerState.paused = false;\n btnStart.disabled = true;\n btnStart.textContent = ‘Running…’;\n btnPause.disabled = false;\n statusEl.classList.remove(‘done’);\n if (tickHandle) clearInterval(tickHandle);\n tickHandle = setInterval(updateTimerDisplay, 500);\n updateTimerDisplay();\n saveState();\n if (typeof gtag === ‘function’){\n gtag(‘event’, ‘timer_start’, {event_category:’interactive_content’, event_label:’cold_brew_steep_timer’, value: Math.round(timerState.totalMs/60000)});\n }\n }\n function pauseTimer(){\n if (!timerState.running) return;\n timerState.pausedRemaining = remaining();\n timerState.running = false;\n timerState.paused = true;\n btnStart.disabled = false;\n btnStart.textContent = ‘Resume’;\n btnPause.disabled = true;\n statusEl.textContent = ‘Paused’;\n if (tickHandle){ clearInterval(tickHandle); tickHandle = null; }\n updateTimerDisplay();\n saveState();\n }\n function resetTimer(){\n timerState.running = false;\n timerState.paused = false;\n timerState.pausedRemaining = 0;\n timerState.startedAt = 0;\n var hrs = parseFloat(steepIn.value) || 14;\n timerState.totalMs = Math.round(hrs * 3600 * 1000);\n btnStart.disabled = false;\n btnStart.textContent = ‘Start Timer’;\n btnPause.disabled = true;\n statusEl.textContent = ‘Timer reset. Adjust inputs then press Start.’;\n statusEl.classList.remove(‘done’);\n if (tickHandle){ clearInterval(tickHandle); tickHandle = null; }\n updateTimerDisplay();\n saveState();\n }\n\n loadState();\n // Restore running/paused state if user re-opens the page\n if (timerState.running){\n // Recompute display\n if (tickHandle) clearInterval(tickHandle);\n tickHandle = setInterval(updateTimerDisplay, 500);\n btnStart.disabled = true;\n btnStart.textContent = ‘Running…’;\n btnPause.disabled = false;\n } else if (timerState.paused){\n btnStart.textContent = ‘Resume’;\n btnPause.disabled = true;\n }\n updateTimerDisplay();\n\n btnStart.addEventListener(‘click’, startTimer);\n btnPause.addEventListener(‘click’, pauseTimer);\n btnReset.addEventListener(‘click’, resetTimer);\n\n [grindSel, ratioSel, tgtSel, tempSel, steepIn].forEach(function(el){\n var ev = (el.tagName === ‘INPUT’) ? ‘input’ : ‘change’;\n el.addEventListener(ev, renderPrediction);\n });\n\n renderPrediction();\n\n // Analytics\n var tracked = false;\n function trackOnce(){\n if (tracked) return;\n tracked = true;\n if (typeof gtag === ‘function’){\n gtag(‘event’, ‘calculator_interact’, {\n event_category: ‘interactive_content’,\n event_label: ‘cold_brew_steep_timer_strength’,\n non_interaction: false\n });\n }\n }\n [grindSel, ratioSel, tgtSel, tempSel, steepIn, btnStart, btnPause, btnReset].forEach(function(el){\n if (!el) return;\n var ev = (el.tagName === ‘INPUT’) ? ‘input’ : ‘change’;\n if (el.tagName === ‘BUTTON’) ev = ‘click’;\n el.addEventListener(ev, trackOnce);\n });\n\n if (typeof IntersectionObserver !== ‘undefined’){\n var vo = new IntersectionObserver(function(entries){\n entries.forEach(function(e){\n if (e.isIntersecting \u0026\u0026 typeof gtag === ‘function’){\n gtag(‘event’, ‘calculator_view’, {\n event_category: ‘interactive_content’,\n event_label: ‘cold_brew_steep_timer_strength’,\n non_interaction: true\n });\n vo.disconnect();\n }\n });\n }, { threshold: 0.5 });\n vo.observe(root);\n }\n })();\n \u003c/script\u003e\n\u003c/div\u003e”,”version”:”1.0″} /–>

Discover more from That's Cold Brew Coffee

Subscribe now to keep reading and get access to the full archive.

Continue reading