Little's law, L=λE[T], connects average concurrency, flow rate and mean residence time. Its strength comes from consistent accounting boundaries. It requires neither Poisson arrivals nor an exponential service distribution, and its mean cannot be replaced by p99.
Why area equals accumulated residence time
Within [0,H], job i contributes the indicator 1{ai≤t<di} to occupancy. Sum over jobs and integrate:
∫0HN(t)dt=i∑∣[ai,di)∩[0,H]∣.
This is a sample-path identity without probability assumptions. Both sides have units job-seconds. Intersections include the part of any job that arrived before the window or finishes after it.
If the system is empty at both endpoints and all m arrivals depart within the window:
NˉH=H1i=1∑mTi=Hm⋅m1i=1∑mTi.
For a stable system with well-defined long-run rates and mean residence times, and suitable conditions making residual boundary contributions divided by H vanish, this gives L=λE[T]. Stationarity is a common analytical setting, not a prerequisite for the finite-window area identity. A short interval that looks calm does not establish the required limits.
What a cutoff leaves out
The experiment uses arrivals [0,1,2,6] s and services [3,1,2,1] s. At H=5 s, two jobs have finished. Occupancy area is 3+3+3=9 job-seconds, so average concurrency is 1.8. Completion rate is 2/5 and completed-job mean residence is 3 s; their product is only 1.2. The missing 3 job-seconds belong to the third, unfinished request.
Preparing the visual
Little’s law and measurement boundaries · Experiment
Fixed arrivals [0,1,2,6] s and service times [3,1,2,1] s. Move the cutoff to compare full occupancy area with completed-job contributions. Initially empty, no rejection.
{"id":"queueing-02","title":"Little’s law and measurement boundaries · Experiment","summary":"Fixed arrivals [0,1,2,6] s and service times [3,1,2,1] s. Move the cutoff to compare full occupancy area with completed-job contributions. Initially empty, no rejection.","height":1000,"html":"<p class=\"intro\" data-i18n=\"scope\"></p><div class=\"controls\"><div class=\"control\"><label for=\"parameter-0\" data-i18n=\"windowEnd\"></label><output id=\"value-0\" for=\"parameter-0\"></output><input type=\"range\" id=\"parameter-0\" data-parameter=\"0\" min=\"3\" max=\"9\" step=\"0.5\" value=\"5\"/></div></div><div class=\"tools\"><button id=\"reset\" data-i18n=\"reset\"></button></div><div id=\"timeline\"></div><div id=\"plots\"></div><div id=\"metrics\"></div><p id=\"status\" role=\"status\" aria-live=\"polite\"></p>","css":".plotarea{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:8px}.yticks{display:flex;flex-direction:column;justify-content:space-between;padding:3px 0;font-size:13px;font-variant-numeric:tabular-nums;color:var(--muted)}*{box-sizing:border-box}.intro{margin:0 0 16px;color:var(--muted);line-height:1.65}.controls{display:grid;gap:14px}.control{display:grid;grid-template-columns:1fr auto;gap:6px 12px;align-items:center}.control label{font-size:16px;line-height:1.5}.control input{grid-column:1/-1;width:100%;min-height:28px}.control output{font-variant-numeric:tabular-nums}.control select{grid-column:1/-1;width:100%;max-width:100%;padding:8px;font:inherit;background:var(--surface);color:var(--ink);border:1px solid var(--rule);border-radius:6px}.tools{margin:14px 0}.tools button{font:inherit;padding:8px 14px;min-height:42px}.chart{margin:22px 0}.chart h3{font-size:16px;margin:0 0 6px;line-height:1.5}.chart svg{display:block;width:100%;height:160px;overflow:hidden}.scale,.axes{font-size:14px;color:var(--muted);font-variant-numeric:tabular-nums}.axes{display:flex;justify-content:space-between;align-items:start;gap:8px;margin-top:6px}.axes span:nth-child(2){text-align:center;flex:1;min-width:0}.legend{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:8px;font-size:15px}.legend span{display:inline-flex;align-items:center;gap:7px}.legend i{display:inline-block;width:22px;flex-shrink:0}#metrics{border-top:1px solid var(--rule);padding-top:12px;display:grid;gap:8px}#metrics>div{display:flex;justify-content:space-between;gap:16px;font-size:15px;line-height:1.5}#metrics strong{font-weight:600;font-variant-numeric:tabular-nums;flex-shrink:0}#status{font-size:14px;color:var(--muted);min-height:3em;margin:14px 0 0}button:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:3px}@media(max-width:420px){.chart svg{height:145px}#metrics>div{flex-wrap:wrap;gap:3px 12px}}\n.job{margin:16px 0}.job-title{font-size:16px;font-weight:600}.job-times{display:flex;flex-wrap:wrap;gap:4px 16px;font-size:14px;color:var(--muted);line-height:1.6}.job svg{display:block;width:100%;height:26px;margin:6px 0}.timeline-key{display:flex;flex-wrap:wrap;gap:12px;font-size:15px;line-height:1.5}.timeline-key span{border-left:12px solid var(--accent);padding-left:6px}.timeline-key span:first-child{border-color:var(--muted)}\n.presets{display:flex;flex-wrap:wrap;grid-column:1/-1;gap:8px}.presets button{font:inherit;line-height:1.5;min-height:44px;max-width:100%;padding:8px 12px;border:1px solid var(--rule);border-radius:6px;color:var(--ink);background:var(--surface);text-align:left;white-space:normal}.presets button[aria-pressed=\"true\"]{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}\n","js":"const MODEL_ID=2, DEFAULTS=[5];\n/* Independent teaching models. Seconds, jobs, and jobs/second unless stated.\n Pure functions: no DOM, clock, timers or external packages. */\nconst mean=a=>a.reduce((s,x)=>s+x,0)/a.length;\nconst curve=(name,points)=>({name,points});\nconst chart=(title,xlabel,curves,extra={})=>({title,xlabel,curves,...extra});\nfunction fifo(arrivals,services){let end=0;return arrivals.map((a,i)=>{const b=Math.max(a,end);end=b+services[i];return {id:i+1,a,b,d:end,s:services[i],w:b-a,t:end-a};});}\nfunction stepCounts(jobs,end,key){const events=jobs.map(j=>j[key]).filter(t=>t<=end).sort((a,b)=>a-b),p=[[0,0]];let n=0;for(const t of events)p.push([t,n],[t,++n]);p.push([end,n]);return p;}\nfunction occupancy(jobs,end){const ev=jobs.flatMap(j=>[[j.a,1],[j.d,-1]]).filter(e=>e[0]<=end).sort((a,b)=>a[0]-b[0]||a[1]-b[1]);let n=0;const p=[[0,0]];for(const [t,v]of ev){p.push([t,n],[t,n+v]);n+=v;}p.push([end,n]);return p;}\nfunction erlangC(c,rho){const a=c*rho;let term=1,sum=1;for(let n=1;n<c;n++){term*=a/n;sum+=term;}const tail=term*a/c/(1-rho);return tail/(sum+tail);}\nfunction finiteQueue(lambda,mu,K){const rho=lambda/mu;const w=Array.from({length:K+1},(_,n)=>rho**n),z=w.reduce((s,x)=>s+x,0),prob=w.map(x=>x/z),block=prob[K],through=lambda*(1-block),L=prob.reduce((s,x,n)=>s+n*x,0);return {prob,block,through,L,W:L/through,busy:1-prob[0]};}\nfunction seeded(seed){let s=seed>>>0;return()=>{s=(Math.imul(1664525,s)+1013904223)>>>0;return (s+.5)/4294967296;};}\nfunction simulateMM1(rho,count,seed){const random=seeded(seed),arr=[],srv=[];let t=0;for(let i=0;i<count;i++){t+=-Math.log(random())/(100*rho);arr.push(t);srv.push(-Math.log(random())/100);}return fifo(arr,srv);}\nfunction queueModel(id,p){let plots=[],metrics={},rows;\n if(id===1){const [gap,long]=p,jobs=fifo([0,gap,2*gap,3*gap],[long,1,1,1]),end=jobs.at(-1).d;\n rows=jobs;plots=[chart('cumulative','timeAxis',[curve('arrivals',stepCounts(jobs,end,'a')),curve('starts',stepCounts(jobs,end,'b')),curve('departures',stepCounts(jobs,end,'d'))])];metrics={meanWait:mean(jobs.map(j=>j.w)),meanTime:mean(jobs.map(j=>j.t)),lastDeparture:end};\n }else if(id===2){const [end]=p,jobs=fifo([0,1,2,6],[3,1,2,1]),overlap=j=>Math.max(0,Math.min(j.d,end)-j.a),area=jobs.reduce((s,j)=>s+overlap(j),0),done=jobs.filter(j=>j.d<=end),completedArea=done.reduce((s,j)=>s+j.t,0);\n plots=[chart('occupancy','timeAxis',[curve('inSystem',occupancy(jobs,end))],{yrange:[0,4]})];metrics={area,meanN:area/end,completedProduct:completedArea/end,boundaryArea:area-completedArea,completed:done.length};\n }else if(id===3){const [rate,window]=p,m=rate*window,prob=[];let q=Math.exp(-m);for(let n=0;n<=Math.ceil(m+7*Math.sqrt(m)+5);n++){if(n)q*=m/n;prob.push([n,q]);}\n plots=[chart('countProbability','countAxis',[curve('poisson',prob)],{bars:true,yrange:[0,Math.max(...prob.map(x=>x[1]))*1.12]}),chart('gapSurvival','timeAxis',[curve('exponential',Array.from({length:101},(_,i)=>{const t=5/rate*i/100;return [t,Math.exp(-rate*t)];}))],{yrange:[0,1]})];metrics={countMean:m,countVariance:m,emptyProbability:Math.exp(-m),meanGap:1/rate};\n }else if(id===4){const [rho]=p,delta=100*(1-rho),q99=Math.log(100)/delta;\n plots=[chart('tailProbability','timeAxis',[curve('systemTail',Array.from({length:101},(_,i)=>{const t=q99*1.2*i/100;return [t,Math.exp(-delta*t)];})),curve('waitingTail',Array.from({length:101},(_,i)=>{const t=q99*1.2*i/100;return [t,rho*Math.exp(-delta*t)];}))],{yrange:[0,1]}),chart('latency','rhoAxis',[curve('meanTime',Array.from({length:95},(_,i)=>{const r=(i+1)/100;return [r,1/(100*(1-r))];})),curve('p99',Array.from({length:95},(_,i)=>{const r=(i+1)/100;return [r,Math.log(100)/(100*(1-r))];}))],{event:rho})];metrics={utilization:rho,meanTime:1/delta,meanWait:rho/delta,p99:q99,zeroWait:1-rho,meanN:rho/(1-rho)};\n }else if(id===5){const [ca2,cs2]=p,points=f=>Array.from({length:95},(_,i)=>{const r=(i+1)/100;return[r,f(r)];}),pk=r=>r/(1-r)*(1+cs2)/2*.01,king=r=>r/(1-r)*(ca2+cs2)/2*.01;\n plots=[chart('waitingSeconds','rhoAxis',[curve('poissonReference',points(pk)),curve('kingman',points(king))],{event:.8})];metrics={pkWait:pk(.8),kingmanWait:king(.8),secondMoment:(1+cs2)*.0001,residualBusy:(1+cs2)*.005};\n }else if(id===6){const [c,rho]=p,C=erlangC(c,rho),wait=C/(c*100*(1-rho));\n const pts=f=>Array.from({length:95},(_,i)=>{const r=(i+1)/100;return[r,f(r)];});\n plots=[chart('waitingSeconds','rhoAxis',[curve('pooled',pts(r=>erlangC(c,r)/(c*100*(1-r)))),curve('split',pts(r=>r/(100*(1-r))))],{event:rho})];metrics={arrivalRate:c*100*rho,waitProbability:C,pooledWait:wait,splitWait:rho/(100*(1-rho)),meanTime:wait+.01};\n }else if(id===7){const [long,policy]=p,sizes=[long,1,1,1,1],order=policy?[1,2,3,4,0]:[0,1,2,3,4];let end=0;rows=order.map(i=>{const b=end;end+=sizes[i];return{id:i+1,a:0,b,d:end,s:sizes[i],w:b,t:end};});\n plots=[];metrics={meanWait:mean(rows.map(j=>j.w)),meanTime:mean(rows.map(j=>j.t)),longWait:rows.find(j=>j.id===1).w,maxSlowdown:Math.max(...rows.map(j=>j.t/j.s)),makespan:end};\n }else if(id===8){const [lambda,K]=p,q=finiteQueue(lambda,100,K);\n plots=[chart('stateProbability','countAxis',[curve('stationary',q.prob.map((v,n)=>[n,v]))],{bars:true})];metrics={offeredRatio:lambda/100,blocking:q.block,acceptedRate:q.through,meanN:q.L,meanTime:q.W,utilization:q.busy};\n }else if(id===9){const [lambda,feedback]=p,visit=1/(1-feedback),local=lambda*visit,stable=local<100,demands=[visit*.01,visit*.004];\n plots=[chart('utilizationChart','nodeAxis',[curve('nodeLoad',[[1,lambda*demands[0]],[2,lambda*demands[1]]])],{bars:true,yrange:[0,Math.max(1.2,lambda*demands[0]*1.1)]})];metrics={visits:visit,internalRate:local,bottleneckBound:100/visit,stable,networkTime:stable?visit*(1/(100-local)+1/(250-local)):null};\n }else if(id===10){const [rho,count,seed]=p,jobs=simulateMM1(rho,count,seed),warm=Math.floor(count/5),cohort=jobs.slice(warm),end=jobs.at(-1).a,done=cohort.filter(j=>j.d<=end),times=cohort.map(j=>j.t).sort((a,b)=>a-b),q99=times[Math.ceil(.99*times.length)-1],theory=1/(100*(1-rho)),max=Math.max(q99*1.2,theory*Math.log(100)*1.2);\n let ix=0;const ecdf=Array.from({length:151},(_,i)=>{const t=max*i/150;while(ix<times.length&×[ix]<=t)ix++;return[t,1-ix/times.length];});\n plots=[chart('tailProbability','timeAxis',[curve('empirical',ecdf),curve('stationaryTheory',Array.from({length:151},(_,i)=>{const t=max*i/150;return[t,Math.exp(-t/theory)];}))],{yrange:[0,1]})];metrics={sampleCount:times.length,censored:cohort.length-done.length,cohortMean:mean(times),completedMean:done.length?mean(done.map(j=>j.t)):null,theoryMean:theory,sampleP99:q99,theoryP99:theory*Math.log(100)};\n }else if(id===11){const [capacity,prob]=p,base=60,factor=1+prob+prob*prob,normal=base*factor,burst=120*factor,Qpeak=Math.max(0,burst-capacity)*30,drain=capacity>normal?Qpeak/(capacity-normal):Qpeak===0?0:null;\n const pts=Array.from({length:241},(_,i)=>{const t=i,rate=t<20||t>=50?normal:burst;let Q;if(t<20)Q=Math.max(0,normal-capacity)*t;else if(t<50)Q=Math.max(0,normal-capacity)*20+Math.max(0,burst-capacity)*(t-20);else Q=Math.max(0,Math.max(0,normal-capacity)*20+Qpeak+(normal-capacity)*(t-50));return [t,Q];});\n plots=[chart('backlog','timeAxis',[curve('queuedWork',pts)]),chart('rateChart','timeAxis',[curve('attemptRate',[[0,normal],[20,normal],[20,burst],[50,burst],[50,normal],[240,normal]]),curve('capacity',[[0,capacity],[240,capacity]])])];metrics={attemptFactor:factor,normalAttempts:normal,burstBacklog:pts[50][1],canDrain:capacity>normal,drainSeconds:capacity>normal?pts[50][1]/(capacity-normal):null,endBacklog:pts.at(-1)[1]};\n }\n return {plots,metrics,...(rows?{rows}:{})};\n}\nif(typeof module!=='undefined')module.exports={queueModel,fifo,erlangC,finiteQueue,simulateMM1};\n\nconst $=s=>document.querySelector(s), colors=['var(--accent)','#b77818','#269282'],dash=['','7 4','2 4'];\nconst number=x=>Number.isFinite(x)?viz.number(x,Number.isInteger(x)?0:Math.abs(x)<.01&&x!==0?5:3):'—';\nfunction plot(parent,spec){\n const all=spec.curves.flatMap(s=>s.points),xx=all.map(p=>p[0]),yy=all.map(p=>p[1]);\n const zero=true;\n let lo=zero?Math.min(0,...yy):Math.min(...yy),hi=zero?Math.max(0,...yy):Math.max(...yy),xmin=Math.min(...xx),xmax=Math.max(...xx);\n if(hi===lo)hi=lo+1;if(xmin===xmax)xmax=xmin+1;\n const pad=(hi-lo)*.08;lo=Math.max(0,lo-pad);hi+=pad;\n if(spec.bars){xmin-=.5;xmax+=.5;}if(spec.xrange)[xmin,xmax]=spec.xrange;if(spec.yrange)[lo,hi]=spec.yrange;\n const sx=x=>8+(x-xmin)/(xmax-xmin)*584,sy=y=>8+(hi-y)/(hi-lo)*144;\n const section=document.createElement('section');section.className='chart';\n const title=document.createElement('h3');title.textContent=viz.t(spec.title);section.append(title);\n const scale=document.createElement('div');scale.className='scale';scale.textContent=viz.t('vertical')+' '+number(lo)+' … '+number(hi);section.append(scale);\n const ns='http://www.w3.org/2000/svg',svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 600 160');svg.setAttribute('preserveAspectRatio','none');svg.setAttribute('role','img');svg.setAttribute('aria-label',viz.t(spec.title));\n function node(name,attrs){const n=document.createElementNS(ns,name);for(const [k,v]of Object.entries(attrs))n.setAttribute(k,v);svg.append(n);return n;}\n for(let j=0;j<=4;j++)node('line',{x1:8,x2:592,y1:8+j*36,y2:8+j*36,stroke:'var(--rule)','stroke-width':1});\n if(lo<=0&&hi>=0)node('line',{x1:8,x2:592,y1:sy(0),y2:sy(0),stroke:'var(--muted)','stroke-width':1});\n if(spec.scatter)node('line',{x1:sx(0),x2:sx(0),y1:8,y2:152,stroke:'var(--muted)','stroke-width':1});\n if(spec.event!==undefined&&spec.event>=xmin&&spec.event<=xmax)node('line',{x1:sx(spec.event),x2:sx(spec.event),y1:8,y2:152,stroke:'var(--muted)','stroke-dasharray':'3 3'});\n spec.curves.forEach((s,i)=>{if(spec.bars){for(const [x,y]of s.points)node('line',{x1:sx(x),x2:sx(x),y1:sy(0),y2:sy(y),stroke:colors[i%3],'stroke-width':Math.min(16,400/s.points.length)});}else if(spec.scatter||spec.dots===i){for(const [x,y] of s.points)node('circle',{cx:sx(x),cy:sy(y),r:spec.scatter?5:2.8,fill:colors[i%3]});}else{let pts=s.points;if(spec.title==='replicas')pts=pts.flatMap((p,j)=>j?[[p[0],pts[j-1][1]],p]:[p]);node('path',{d:pts.map(([x,y],j)=>(j?'L':'M')+sx(x).toFixed(2)+' '+sy(y).toFixed(2)).join(' '),fill:'none',stroke:colors[i%3],'stroke-width':2,'stroke-dasharray':dash[i%3],'vector-effect':'non-scaling-stroke'});}});\n const area=document.createElement('div');area.className='plotarea';const ticks=document.createElement('div');ticks.className='yticks';for(const v of [hi,(hi+lo)/2,lo]){const label=document.createElement('span');label.textContent=number(v);ticks.append(label);}area.append(ticks,svg);section.append(area);\n const axes=document.createElement('div');axes.className='axes';const fmt=x=>spec.logx?number(10**x):number(x);\n for(const text of [fmt(spec.bars?xmin+.5:xmin),viz.t(spec.xlabel),fmt(spec.bars?xmax-.5:xmax)]){const a=document.createElement('span');a.textContent=text;axes.append(a);}section.append(axes);\n const legend=document.createElement('div');legend.className='legend';spec.curves.forEach((s,i)=>{const el=document.createElement('span'),sw=document.createElement('i');sw.style.borderTop=`3px ${i===0?'solid':i===1?'dashed':'dotted'} ${colors[i%3]}`;el.append(sw,document.createTextNode(viz.t(s.name)));legend.append(el);});section.append(legend);parent.append(section);\n}\nfunction timeline(rows){\n const host=$('#timeline');host.replaceChildren();if(!rows.length)return;\n const key=document.createElement('div');key.className='timeline-key';for(const k of ['waiting','service']){const e=document.createElement('span');e.textContent=viz.t(k);key.append(e);}host.append(key);\n const max=Math.max(...rows.map(r=>r.d)),ns='http://www.w3.org/2000/svg';\n for(const r of rows){const box=document.createElement('div');box.className='job';const title=document.createElement('div');title.className='job-title';title.textContent=viz.t('job')+' '+r.id;box.append(title);\n const svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 600 26');svg.setAttribute('preserveAspectRatio','none');svg.setAttribute('role','img');svg.setAttribute('aria-label',viz.t('waiting')+' '+number(r.w)+' s; '+viz.t('service')+' '+number(r.s)+' s');\n for(const [a,b,color]of [[r.a,r.b,'var(--muted)'],[r.b,r.d,'var(--accent)']]){const rect=document.createElementNS(ns,'rect');rect.setAttribute('x',a/max*600);rect.setAttribute('y','3');rect.setAttribute('width',(b-a)/max*600);rect.setAttribute('height','20');rect.setAttribute('fill',color);svg.append(rect);}box.append(svg);\n const text=document.createElement('div');text.className='job-times';for(const [k,val]of [['arrivalTime',r.a],['startTime',r.b],['endTime',r.d]]){const e=document.createElement('span');e.textContent=viz.t(k)+' '+number(val)+' s';text.append(e);}box.append(text);host.append(box);\n }const axis=document.createElement('div');axis.className='axes';for(const s of ['0',viz.t('timeAxis'),number(max)]){const e=document.createElement('span');e.textContent=s;axis.append(e);}host.append(axis);\n}\n\nfunction update(){\n const values=[...document.querySelectorAll('[data-parameter]')].map(e=>+(e.matches('.presets')?e.dataset.value:e.value));\n document.querySelectorAll('input[data-parameter]').forEach(e=>$('#value-'+e.dataset.parameter).textContent=number(+e.value));\n document.querySelectorAll('[data-choice]').forEach(b=>b.setAttribute('aria-pressed',String(b.dataset.choice===b.parentElement.dataset.value)));\n const r=queueModel(MODEL_ID,values);document.body.dataset.result=JSON.stringify(r.metrics);document.body.dataset.parameters=JSON.stringify(values);\n timeline(r.rows||[]);$('#plots').replaceChildren();r.plots.forEach(s=>plot($('#plots'),s));$('#metrics').replaceChildren();\n for(const [k,v]of Object.entries(r.metrics)){const line=document.createElement('div'),label=document.createElement('span'),value=document.createElement('strong');label.textContent=viz.t(k);value.textContent=typeof v==='boolean'?viz.t(v?'yes':'no'):number(v);line.append(label,value);$('#metrics').append(line);}\n $('#status').textContent=viz.t('updated');\n}\ndocument.querySelectorAll('input[data-parameter]').forEach(e=>e.addEventListener('input',update));\ndocument.querySelectorAll('[data-choice]').forEach(b=>b.addEventListener('click',()=>{b.parentElement.dataset.value=b.dataset.choice;update();}));\n$('#reset').addEventListener('click',()=>{document.querySelectorAll('[data-parameter]').forEach((e,i)=>{if(e.matches('.presets'))e.dataset.value=DEFAULTS[i];else e.value=DEFAULTS[i];});update();});update();\n","audio":false,"strings":{"area":"Occupancy area / job·s","boundaryArea":"Unfinished-job contribution / job·s","completed":"Completed jobs","completedProduct":"Completion rate × completed-job mean","inSystem":"In system","meanN":"Mean jobs in system L","no":"No","occupancy":"Jobs in system N(t)","reset":"Reset experiment","scope":"Fixed arrivals [0,1,2,6] s and service times [3,1,2,1] s. Move the cutoff to compare full occupancy area with completed-job contributions. Initially empty, no rejection.","timeAxis":"Time / s","updated":"Recalculated; — means not applicable under these conditions.","vertical":"Vertical range","windowEnd":"Observation cutoff / s","yes":"Yes"}}
Move the endpoint to 7 s or later. All jobs have finished and the boundary contribution becomes zero. Extending the window further adds idle time, reducing both estimates together. Observation length is part of the measurement definition.
Match boundary, population and time range
A stable pool accepting 200 requests/s with mean connection holding time 40 ms has mean occupied connections 200×0.040=8. This does not say that configuring eight connections meets a waiting target: average occupancy leaves no explicit allowance for fluctuations.
For the waiting area, use Lq=λE[Wq]; for the entire system, use L=λE[T]. If only 800/s of 1000/s offered requests enter, use 800/s with internal occupancy. When exits include failures or cancellations, residence statistics must include the same exit population. Successful throughput cannot be paired directly with occupancy of all attempts.
Retries create multiple attempts per business request. A business boundary can span first submission to final outcome; an attempt boundary tracks each entry and exit separately. Either is useful, but their counts and durations cannot be mixed.
Check your understanding
Average concurrency is 50 and throughput 500/s. Is p99 therefore 100 ms?
Reasoning
Only the mean system time follows as 100 ms, provided long-run boundaries match. Many distributions share a mean and have different tails. A percentile requires request-level distribution data or another model.
During an incident, occupancy rises but completed-job mean latency stays low. Has Little's law failed?
Reasoning
Slow requests may still be unfinished and absent from completed-job statistics. Instantaneous occupancy also differs from time-average occupancy. Check window area, unfinished residence contributions and jobs entering before the window before applying a long-run formula.
Further reading
The MIT 2026 queueing lecture discusses Little's law and subsystem boundaries. The cutoff example above follows directly from its independently constructed timestamps.