You Shall Not (have a) Pass!: Managing Queue Congestion at Deer Valley
python
optimization
operations
Author
Brandon Scott
Published
October 30, 2026
Introduction
Silver Mine to Silver Lake
Park City wasn’t always known for being a ski town. In fact, it wasn’t built to be a ski town. In the early 1930s, Park City was a mining town for silver. It wasn’t until 1936 that projects on the mountains in Park City began. The Works Progress Administration (WPA) began the initiative of creating ski runs to help stimulate the economy during the winter. While the operations were fairly small and modest, two residents saw the potential.
In 1946, Bob Burns and Otto Carpenter saw the ski runs and thought there could some for fun on the hills. To do this, they utilized old mining equipment to build a mechanized ski lift. The lift became the first operating system at the offical Snow Park Ski Area. However, the operation remained small-scale. That is until the early 1970s.
Two luxury real estate brokers, Edgar and Polly Stern, visited Park City. They fell in love with the area and saw major potential for a luxury ski experience. They purchase Snow Park and Treasure Mountain to begin their vision. In 1981, Deer Valley Resort officially opened with 5 chairlifts, 35 runs, and two main lodges (Snow Park and Silver Lake). After a few years of operations, Deer Valley began attracting big stars like Roger Penske to join the lodge. Soon, olympians, celebrities, and other wealthy business people were skiing at Deer Valley.
In 2017, Deer Valley was acquired by KSL Capital Partners and Henry Crown and Company, which eventually became Alteraa Mountain Company. While part of the broader network of Alterra ski resorts, Deer Valley stands alone as one of the top, if not the top, luxury ski resort in the United States.
The Uniqueness of Deer Valley
There are few things that really make Deer Valley, well, Deer Valley. First, if you’ve noticed so far, we’ve only chatted about skiing. Well, that is because Deer Valley is a Ski Only Resort. That’s right, snowboarders are not allowed, ever. Before you get too up in arms about the injustice to snowboarders, this was actually fairly common back in the 1980s (according to AI). Deer Valley just decided to keep the rule in place to differentiate themselves from the market. This differentiation actually does resound with a lot of high-end skiers that want ski only “vibes” if you will (you can google these reasons on your own).
Another thing the really differentiates Deer Valley and sets them as a premium luxury ski experience, is the limit of lift tickets/guest admittance. Deer Valley believes that to enhance the guest experience, the mountain should feel like an exclusive location. This focus on providing an exclusive, premium, and luxury ski experience is what attracts Deer Valley skiers every year.
Operating Luxury
How many tickets do we sell?
While Deer Valley’s goal is to optimize luxury ski experiences through exclusive mountain access, they are also still a business. They have operating costs, marketing expenses, stakeholders looking for returns on investment, etc. So, we have an entity that wants to minimize the supply of a given good, while still maximizing the revenue generated from the good. So what is the right number of tickets to sell? In order to solve this, we need a guiding metric that is influenced by our overall strategy.
To repeat it, the overall strategy and position of Deer Valley is to differentiate itself from other ski resorts by providing premium, luxury, and exclusive ski experiences. The hope of this is to generate a high enough experience to maximize loyalty (returned skiers) each year. What is one thing that detracts from a good experience on a ski trip? Waiting in line. It’s the same on any trip really. People who expect exclusive experiences do not want to wait in line for awhile. They want to pay a premium to be able to access their resort and maximize time on it.
This brings us to the real question we want to answer. If we believe that increased wait time provides a negative experience, then we just need to minimize this experience, constrained by the needs of the business (i.e. find a breakeven point). We then can charge a premium from there in order to gain profit on our services. Great! But how do we derive any of that?
Finding the constraints
Let’s start with identifying the constraints of the business. That is, we don’t ever want to operate the resort at a loss (it wouldn’t matter if we were maximizing CLTV if we went out of business ya know). To make our problem simple, let’s assume that Deer Valley operates only on fixed costs and make that average fixed cost a flat number of $1 million a day. Let’s also assume that the average day pass is $200. The math is trivial at this point(divide cost by average pass amount) to get 5000 tickets. We can use this number as our lower bound for the rest of our analysis.
Not bad! We have a number now. A next good question could be what is the total capacity of the ski resort? In this case, we define capacity as the number of people processed (in aggregate) at ski lifts over some interval of time. Fortunately for us, Deer Valley actually lists on their site their uphill lift capacity (check it out here under winter mountain stats). The max capacity of Deer Valley uphill lift is 85,360 guests per hour. This number represents basically if every chair on every lift was filled and ran in parallel, it could theoretically process this many people in an hour.
Cool, so now what? We have the theoretical max that could fit on those chairs in a given hour, but how does that help us with figuring out our ticket number? Because knowing the capacity of our system helps us to identify (theoretically) the wait that customers endure. We know we can process that many guests, but how long would a guest have to wait before being served?. We can back this number out of our capacity finding.
For simplicity, let’s assume we are looking at a high-speed quad lift and assume it is operating at max capacity. So, at the end of the ski lift, 4 people will be dismounting. Let’s also assume that this quad lift has 100 carriers. So, at most 400 people can be on this lift at a time. If we assume that each lift at the resort is carrying the “average” load (total capacity / all lifts), this lift would need to process about 2753 people per hour. If we have 100 carriers, each fitting 4 people, and need to drop off 2753 people at the top of our lift in one hour, at what interval would these carriers need to arrive? Moving this 2753 number to guests per minute, we need about 45 guests per minute. At 4 people per carrier, we need about 11 carriers to dump their guests off in this 60 second window. This comes to about 1 carrier every 5.45 seconds (essentially 6 seconds). Below is a sample video of what this would look like.
Phew, we made it through all that math (for now). But why do we care about how often a carrier drops off their guests? Because that is the exact time it takes for a carrier to pick up its guests! If you managed to get at the lift right as a carrier is leaving, and no one was in front of you (and you also had no trouble getting to the boarding area), you’d only have to wait about 6 seconds. Furthermore, this is now the established rate at which a queue can be cleared. That is, 4 people gone from the queue every 6 seconds.
Measuring Utility
In the previous section we layed out some important information. We need to sell at least 5000 tickets (according to our assumptions) to breakeven. We discovered the max capacity of the resort and calculated the throughput capacity of a quad lift (1 carrier every 6 seconds, or 4 people every 6 seconds). So, guests know that the queue will move forward every 6 seconds, regardless of how many people are in front of them. With all this information, let’s remember what we’re trying to accomplish.
From a business level, we want to sell the most amount of tickets without ruining the experience for guests via long wait times. This number has to be at least 5000. But what’s the upper bound? How would we even begin to figure that out?
This is where Naor’s model [1] comes in handy. In 1969, economist Paul Naor released his paper The Regulation of Queue Size by Levitating Control. Naor was the first (at least credit wise given) to view queues as an economic problem and not just a probability problem. Basically, Naor said when people choose lines, they weigh tradeoffs… that is, does the reward at the end of this line outweigh the cost of waiting in it?
From a more formal mathematical perspective, let R represent the utility reward one receives at the end of the line and let C be the cost of waiting for a single unit of time. Let’s also let \mu be the service time. Suppose we arrive at a line and there are i number of people in front of us, making us the i+1 person to join the queue. The net utility then for waiting in this line would be R - \frac{C(i+1)}{\mu}. This formula just means that the leftover utility (reward) from waiting in this line depends on the service rate and the total cost of waiting due to the queue.
For example, suppose I’m at Chick-fil-A and my reward is 5, my cost is .5 per minute, the service rate is 1 car per minute, and there are 3 people in front of me. My net utility would be 5 - (.5 * (3+1) / 1) = 3. Using basic microeconomic theory here, since my net utility is greater than 0, a rational (utility maximizing) agent would choose to wait in this line.
This is the beauty and simplicity of Naor’s model. If net utility is greater than 0, you should join the line. If it is less than 0, you shouldn’t join the line since it “won’t be worth it” to you. The current formula of Naor’s model doesn’t intuitively show this, but rearranging it a bit, a threshold value is revealed.
Using this newly rearranged Naor model now, we say a rational agent joins a queue if the the ratio of reward service by cost is greater than the length of the queue when joined. Using my example previously, (5 * 1) / .5 = 10, which is greater than 4, so I should join.
You may be thinking “well this is cool, but how is this more intuitive than the net utility?”. This new form of Naor’s model is great because it gives an explicit threshold (i.e. upper bound) on people joining this queue. Assuming rewards and costs are homogenous and service rate is constant, rational agents will only join the queue if the length of the queue (plus them) is 10. If it is any greater than 10, net utility is negative and therefore “not worth it”.
Utilizing Naor’s Model
Now, Naor’s model was not developed for ski resorts or queueing networks in general. Naor’s model applies to M/M/1 queues where the queue length is observable. It’s a very simple model. How do we then use Naor’s model to guide our model of Deer Valley queue congestion?
Let’s continue to make some assumptions about our environment for modeling sake (yay more assumptions). Let’s assume that any person on the mountain chooses a ski run to get to a certain lift and doesn’t defer from that decision. So, even if the line is really long, they still join the queue. Using Naor’s model, we can say that any queue i (i.e. the lift line) either produces positive net utility or negative net utility (or 0 but we’ll assume that’s rare). Then, at the end of the ski day, an individual’s net utility will either be positive or negative. If it is positive, the skier viewed the service as “worth it” and will return. If it is negative, the skier viewed the service as “not worth it” and will not return (i.e. churn). Formally, the mathematical definition for this process for single skier is shown below.
All Equation 2.1 says is that for any skier, we sum up all their net utilities for all the lift lines they visited throughout the day (n is the number of ski lifts visited). U is the net utility defined as the reward for making it through the line minus the cost per time unit of waiting in the line. This will be our “micro” view of the mountain operations, where each skier has some aggregate net utility they carry throughout the day. To see a more macro view of the net utility across the mountain, we just simply sum all the utilities of each skier. If we have m total skiers on the mountain, then we have
\sum_{j=1}^{m}\sum_{i=1}^{n} U_{i}
These are fairly simple formulas but powerful in understanding the dynamics of the mountain based on our assumptions. Remember from Naor’s model that \frac{R\mu}{C} is the threshold value. If the queue is longer than this threshold value, then (according to this simple model) we will always have a negative net utility for any skier joining this queue. This is great! But why is this great?
This is great because we can use this information in helping us answer our original question of how many tickets to sell! Every ski lift now has a threshold of \frac{R\mu}{C}. If we knew that R was 5 and C was 1, and if we use the service rate provided earlier (40 people every minute), the threshold for every lift would be
200! If I am the 200th person in line, my net utility would be 0 by the time I reach the front and am served. Therefore, so long as there are less than 198 people in front of me, I would still have positive net utility! This obviously is a very conservative example of the use of this model, but it illustrates the point we’re trying to make. Based on this implementation of Naor’s model, there is some fixed threshold that we have to meet in order to generate positive net utility for guests. This also shows that we can “play around” with R and C to provide some sensitivity analysis of our system.
For the purposes of reaching an answer eventually on number of tickets to sell, let’s assume the reward of each ski lift is constant at 2.5, the cost of waiting every minute in line is 1, and the service rate is still 40 per minute. This gives us a threshold value of 100. So, if you were the 99th person to join the line, you’d still end up with some positive utility. If it were the case that you were the 99th person, how long would it take you to be served? Using the (theoretical) service rate of 1.5 seconds per person, you’d be served in about 147 seconds (about 2 minutes and 27 seconds). Is that a realistic time for you to wait in line and still be happy?
Mapping the Network
We’ve established the economic framework to understand how much it “costs” for guests to wait in lines. But guests don’t just spend the entire time in line do they? There are guests on lifts and on runs. As long as the flow of guests from lines to lifts to runs is smooth, we can fit more guests into the resort! Let’s assume that we live in a perfect world where ski lifts don’t break (or stop because people fall off), that lift ride times are deterministic, and that ski run times have a fairly strong average.
First, let’s calculate how long someone would be riding a ski lift on average? Returning to our current timing standard of the quad lift presented at the beginning of this post, we average a carrier about every 6 seconds. There are 100 carriers on this lift. Assuming equal spacing, how long would it take a carrier that just got loaded with passengers to reach the top of the lift? That would mean there are about 49 other carriers in front of us and if all those get served, then we’d be served in 49 * 6 seconds, or 294 seconds. So, this chair we’re following would dump its passengers at the 300th second mark. In total, this chair took 5 minutes to get to the top of the lift.
For the purposes of making our lives easier, let’s assume the average lift follows this speed of 5 minutes. We at least have a 5 minute buffer now between once skiers leave the queue. Now, we need to figure out how long it would take them to get back to a queue. How can we model that? Using classical queue theory, we assume that guests follow a transition matrix. A transition matrix is a mapping of the probabilities of moving from one queue to some other queue. Below is a simple example of this principle.
In the video, we have a 1x3 matrix that shows the probabilities of choosing between 3 routes from node 1. According to the matrix, the orange dot chooses node 2 with probability .25, node 3 at .5, and node 4 at .25. Returning to ski runs, if we say that node 1 is the top of the hill and a skier had 3 ski runs to choose from and followed this transition matrix, we’d expect the path to node 3 to be twice as busy as the runs to node 2 and node 4.
So how does the transition matrix help us calculate time till entry to the queue? By helping us calculate another average! That is, we’d calculate the average travel time using the weights in the transition matrix. Let’s imagine that in the above transition matrix, all three routes led to a single lift. Let’s now assume that it takes 6 minutes to get down path 2, 7 minutes to get down path 3, and 8 minutes to get down path 4. We’d weight these times by the probabilites of a guest choosing that path and sum up those values. In this case, we’d get an average of 7 minutes. If we believe that this number is good representation across the resort, along with the the 5 minutes we solved for earlier, then on average a skier would be out of a queue for 12 minutes.
Trust the Theory
Let’s review all the numbers so far. We (theoretically) process a guest to a lift at 1.5 seconds a pop. From our implementation of Naor’s model, we assume the cost-reward ratio produces positive net utility up to the 99th person in line. The total wait time for the 99th person to be processed would be about 2 minutes and 30 seconds. Guests spend an average of 5 minutes on a lift and from our transition matrix example, guests spend on average 7 minutes skiing, totaling in 12 minutes of average time spent between queues. With all these numbers, can we now converge on a number of tickets to sell? Yes! Well, theoretically at least.
Using the numbers above, we can get a theoretical steady state number of how many people we can fit in our resort. To do this, we’ll use Little’s Law[2] for these steady state calculations. Little’s law is L = \lambda W. That is, L is the average number of people in the system, \lambda is the average arrival rate, and W is the average wait time. This will be useful for us in calculating the number of people in transition in the system.
Ok, let’s do the math. Let’s assume that we always have 100 people in line (but not more). So, 100 people multiplied by 31 queues gets us 3100 people. Each queue processes a person every 1.5 seconds, or about 40 people per minute. Across all 31 queues, we are outputting 1240 people per minute into the transition period between queues. On average, these people wait in the transition period for 12 minutes. See what’s happening? We now have our arrival rate and our average wait time. Multiplying 1240 by 12 gets us 14880 people. Adding that to the people in the queues, we have 17980 guests!
That’s amazing! By breaking down the system capacity into processing times, deriving the wait time utility bounds on guests, and estimating the number of guests in transition, we’ve uncovered an actual number of how many guests we can theoretically have in the resort at once! Remember how we needed at least 5000 guests to break even? Well, if we were to sell this many tickets at Deer Valley, we’d generate revenue of 17980 by 200, which gives us about $3,596,000 in revenue. Using our fixed cost of 1 million, we end up with BANK (you can do the math to find out how much bank).
Phew, we did it. We found the number. Let’s go tell Deer Valley that they are severly underselling (they cap around 7000-8000 tickets) and to sell 10000 more to drive profits. Boom. Let’s go home.
Simulate the Uncertainty
Ok, the scene above was a bit of a stretch, but really… this number 17980, leaves much question. If you actually read through this whole post so far (thanks btw), you would know then that we have made MANY assumptions to get to this number. Some assumptions include: Consistent average lift times across all lifts, the whole implementation of Naor’s model to our system is based on assumptions, the system operating continually in steady state, and the list goes on. While it is entirely practical to make assumptions to reach a number using models, we shouldn’t immediately trust a single number from a model.
What we need now is some evidence that, based on the current system capacities in place, shows that the system behaves according to our assumptions. To do this, we can run simulations. A simulation is nothing more than us taking our model, along with the assumptions, and throwing them into a computer to see how the system “truly” behaves. Obviously simulations should also be taken with a grain of salt, but simulations are great at quantifying uncertainty and checking the robustness of our model, especially in complex environments. Below is a small scale simulation of our ski resort that uses similar parameters to the transition matrix above.
In the simulation above, we have one lift that takes guests to the top of the mountain where they have the choice between 3 ski runs. The weights on each run represents the time it takes a guest to finish that run. With this simulation, we calculate the average travel time (sum of all travel time / number of runs completed) and average wait time (sum of all wait time / number of guests processed).
This simulation returned average travel times of about 4 mins and an average wait time of about 8.5 seconds. Pretty cool that we can simulate this! Obviously this was a small scale demonstration with just 8 guests, but it shows the capability we have in seeing how realistic and robust our system is.
Ok so can we actually simulate our model at the scale of Deer Valley? For sure! Just not as a video (we technically can, but I don’t feel like coding up a whole resort animated simulation). To simulate at the scale of Deer Valley, we’ll use SimPy.
Before jumping right into the simulation and obtaining results, we need to define our model (that’s right, another model). First, we’ll establish the environment. We have 32 lifts at Deer Valley (I know not all of these are the detachable chairlifts, but for ease, let’s roll with 32). We’ll also simplify and say 25 of these are high-speed quads and the other 7 are triple chair lifts. We’ll assume that these operate deterministically at the rates we’ve been using so far (a chair every 6 seconds for the quad and for a bit more reality, a chair every 8 seconds for the triples).
Additionally, we’ll assume each lift leads to a hill top that is connected to 2-3 paths that lead to other lifts at the resort (Note: The topology generated by the simulation does not reflect the topology of Deer Valley). These paths have randomly assigned probabilites. Furthermore, we assume that the time it takes for any given skier is uniform on some bound randomly selected beforehand.
The rate at which skiers enter the resort is staggered across a 2 hour window. The total operational window of the resort is 8 hours (a bit longer than normal day but is what it is). Each skier will ski 4 hours. We run this simulation wiht 5000 skiers, 10000 skiers, 15000 skiers, and 20000 skiers. Below, we show the top 10 lift average wait times (based on the 20000 skier simulation).
# --- SIDE-BY-SIDE COMPARISON ACROSS ALL SCALES (POLARS) ---comparison_table =Nonefor count in scales:# 1. Calculate average wait time for each lift at the current scale df_summary = ( benchmark_results[count]["df"] .group_by("lift_id") .agg(pl.col("wait_time").mean().alias(f"{count} Skiers")) )# 2. Join it into our master comparison tableif comparison_table isNone: comparison_table = df_summaryelse: comparison_table = comparison_table.join(df_summary, on="lift_id", how="inner")# Sort by the largest scenario (20,000 skiers) to highlight the busiest bottlenecks at scalecomparison_table = comparison_table.sort("20000 Skiers", descending=True)print("\n--- Side-by-Side Average Wait Time Comparison Across All Scales ---")print(comparison_table.head(10))
From the table above, in the 20000 simulation, guests on average had to wait 155 minutes at lift 31! Crazy! While the busiest lifts are obviously not the same across the different simulations, we can easily see how increases in guest number drastically increase wait time. To get an idea of the net utility experienced by guests in each of these simulations, we take the average across all guest utilites. This is shown below.
# --- GLOBAL AVERAGE NET UTILITY ACROSS SCALES (POLARS) ---summary_data = {f"{count} Skiers": [benchmark_results[count]["df"]["net_utility"].mean()]for count in scales}# Create a single-row Polars DataFrame comparing all scalessummary_table = pl.DataFrame(summary_data)print("\n--- Overall Average Net Utility Across All Scales ---")print(summary_table)
The only simulation to produce positive net utility was the 5000 simulation. All other simulations produced negative utility! Why could that be? In modeling, the results always return back to the assumptions we put in place. One of the biggest assumptions we made was the flat reward of 2.5 with the cost of waiting being 1 unit per minute. Essentially, we assumed guests could only wait 2.5 minutes before experiencing negative net utility. That’s a fairly strong assumption to make! Even with this fairly conservative reward to cost ratio, we still ended up with positive net utility in one simulation and not “too” bad of negative net utility in the 10000 simulation.
For fun, let’s say that these assumptions accurately reflect reality. Somewhere between 5000 and 10000 skiers we go from positive net utility to negative net utility. How could we find the best number then between this to maximize revenue while still producting “positive” guest experiences? We could keep running our simulation to converge on a number, or we can just use linear interpolation.
Let x be the number of skiers and y be the net utility. We have two points, (5000, 1.89) and (10000, -5.67). We want to find the point where net utility is equal to 0 (this is the most tickets we could sell before breaking into negative net utility). To find this point, we use the formula below.
y = y_{1} + m(x - x_{1})
m in this formula is the slope of the line between our two known points. We set y equal to 0, plug in our values for y_{1}, x_{1}, and m to eventually solve for x. After plugging those in and solving for x. Doing all that, we arrive at ~6250 skiers.
Wow! Our new number says we should only add about 1250 more skiers past our breakeven point. Comparing this to our previous number of essentially 18000 skiers, this is quite the drop in potential revenue. So what gives? Were our steady-state numbers off when arriving at 18000? Not necessarily. Speaking mathematically, both of our models are correct. However, as we mentioned earlier, the assumptions are always the biggest culprit in the difference between model and reality. Our steady-state number of 18000 assumed steady-state. In reality, that rarely ever holds. In our simulation, we assumed we perfectly knew the reward and cost of waiting in line. In reality, we don’t know. But this is actually the beauty of modeling! We make assumptions to make models easier to implement and interpret, but we also learn what assumptions need to break in order to better match reality.
Conclusion
In this blog post, we covered the early history of Deer Valley on how it went from mining town to a booming luxury ski resort. We discussed how we should proceed with modeling ticket sales. We utilized operations numbers to arrive at various different numbers such as the throughput rates of lifts, the wait time of guests, etc. We dove into the idea of optimizing ticket sales by using these operation numbers along with economic theory, assuming that guests incur a cost for waiting and a reward for making it through the line. We implemented this model theoretically and arrived at our first number of ~18000. We didn’t allow ourselves to settle for one number so we pushed further to gain insights into wait times and net utilities using simulations. We compared the two ticket numbers and spoke on why the numbers didn’t agree.
Overall, we hope this blog post illustrated the beauty of modeling. While the math is an important part of modeling, the real game is in identifying the assumptions of your model. Models inherently need assumptions because they are simplifications of reality. As you move forward with your own models, remember to have your models be a guiding influence in your iteration and not be the primary metric that decides your direction.
References
[1]
P. Naor, “The regulation of queue size by levitating control,”Econometrica, vol. 37, no. 1, pp. 15–24, 1969.
[2]
J. D. C. Little, “A proof for the queuing formula: L = w,”Operations Research, vol. 9, no. 3, pp. 383–387, 1961, doi: 10.1287/opre.9.3.383.
If you found this content helpful, consider supporting the journey!