Introduction
The .NET framework contains a large number of collection classes. However, there are a few omissions including the double-ended queue (or deque) which is present in the standard libraries of other languages such as C++ and Java and is particularly useful in job scheduling applications.
In the case of an ordinary queue, items can only be added at the back and retrieved from the front of the queue. However, a deque allows items to be added at and retrieved from either end of the queue.
I thought it would therefore be useful to implement a Deque<T> class in C# to supplement the Queue<T> class which is already present in the .NET Framework.
Implementation
Deques are often implemented using doubly linked lists to which they are closely related. In fact another name for a deque is a 'head-tail linked list'.
The main difference between the two is that you can insert elements into or remove elements from the middle of the linked list as well as at the end points.
However, because of the way that heap memory is allocated, doubly linked lists (namely LinkedList<T>) are not very efficient in .NET and I have therefore used two List<T>'s , placed back to back, instead.

I would have preferred to use back to back queues instead but this wasn't feasible as the items in the queues would need to be accessed by index in the circumstances described later in this section.
One List<T> represents the front and the other represents the back of the deque. Provided that there is always at least one element in both lists, then this approach is about three times faster than using a doubly linked list.
The difficulty, of course, is what to do if you need to remove an element from one end of the deque and there are no elements left in the corresponding list.
What I decided to do is to return the first element of the other list and mark it for deletion but not actually delete it. Deletion is a relatively expensive operation because all the other elements of the list need to be moved down in memory. Also simply marking it for deletion is better than it sounds because the capacity of the list (i.e. the maximum number of elements it can accommodate) is never reduced automatically when deletions take place. So having deleted items still sitting in memory does no immediate harm.
However, it does do some longer-term harm because it reduces the time needed before the capacity of the list next needs to be increased (a very expensive operation) and, in the case of reference type elements, increases the time before they can be garbage collected if there are no other references to them.
Consequently, whenever a new item is added to the deque, if the deque would otherwise need resizing, any deleted items are then removed completely. This postpones the resizing operation and may even prevent it altogether. If there is more than one deleted item, it also means that the other items only need to be moved down in memory once rather than each time a deletion occurs.
Resizing doesn't occur very often. A generic list has a default capacity of 4 and this is doubled (to 8, 16, 32 etc.) as new items are added.
If no further items are likely to be added to the deque, then you can call the TrimExcess method so that the capacity then matches the number of items. This method also removes deleted items before trimming the remainder. Notice though that since this method calls the TrimExcess method of the underlying List<T>'s, trimming only takes place if the List<T> is less than 90 per cent of capacity. In other words at least 10 percent of capacity must be unused.
The Deque<T> class
The Deque<T> class contains the same members, and implements the same interfaces, as the Queue<T> class except that there are no Enqueue and Dequeue methods. I have also added some 'convenience' members which Queue<T> lacks.
I felt that EnqueueFirst, EnqueueLast, DequeueFirst and DequeueLast would be too long-winded for names of commonly used methods and so I have used AddFirst, AddLast, PopFirst and PopLast instead. There are also PeekFirst and PeekLast methods and 'Try' versions of the last four to avoid throwing an exception if the Deque is empty.
The other 'convenience' members are the Capacity and IsEmpty properties and the AddRangeFirst and AddRangeLast methods which add multiple items to the Deque from an enumerable collection.
There is also a Reversed property which enables you to iterate the Deque in reverse order and a Reverse method which permanently reverses the order of the items in the Deque.
This table lists Deque<T>'s main constructors with a brief description of what they do:
| Constructor Signature | Description |
| Deque() | creates a new empty Deque |
| Deque(capacity) | creates a new Deque with the specified initial capacity |
| Deque(backCollection) | creates a new Deque by adding items from backCollection at the back of the Deque |
| Deque(backCollection, frontCollection) | creates a new Deque by adding items from backCollection at the back and items from frontCollection at the front of the Deque |
This table lists its properties:
| Property Name | Description |
| Capacity | gets the total capacity of the Deque |
| Count | gets the total number of items in the Deque |
| IsEmpty | indicates whether the Deque is empty or not |
| Reversed | enables the Deque's items to be iterated in reverse order (from last to first) |


Mary WilliamPosted Jul 10, 2023, 11:56 AM
Good one! informative thanks
Gowtham RajamanickamPosted Mar 13, 2015, 1:54 AM
Good article for learners....
Paul BrenekPosted Oct 27, 2014, 3:58 PM
Thank you for you very helpful replies. I really appreciate them.
VulpesPosted Oct 17, 2014, 1:25 PM
C++ doesn't support properties or indexers as such though you can simulate the latter by overloading the [] operator. It's therefore more natural in that language to use methods for everything.
Paul BrenekPosted Oct 17, 2014, 1:05 PM
As an aside, why does the C++ deque use an at() method rather than an indexer? Clearly they see more value in having a method. Just curious as to your opinion. Thank you.
Paul BrenekPosted Oct 16, 2014, 7:27 PM
Aah. Thank you for your clarification. Makes sense.
VulpesPosted Oct 15, 2014, 5:56 PM
You'd use the indexer like this: dq[i-2] as if the Deque were an array. Just setting the Deque variable to null after you've finished with it (if it's not otherwise about to go out of scope) is all you need to do to ensure the Deque is GC'd as timely as possible. A problem with using Dispose is that the Deque will be left in an unstable state and, if you inadvertently access it again before it's GC'd, it might result in an exception.
Paul BrenekPosted Oct 13, 2014, 4:19 PM
Thank you for your input. I agree with you on the IDisposable. The only reason I did this is because the code will create many instances on the Deque and I thought if I coded it with using{} this would help remove the old instances. Your thoughts on this would be greatly appreciated. Also, thank you for the recode of the At. I really do appreciate it.
VulpesPosted Oct 9, 2014, 7:54 PM
I don't really see the point of implementing IDisposable as the deque doesn't use any unmanaged resources and none of the other collection classes in the .NET framework implement it either. When the deque is GC'd the internal lists will be GC'd.
VulpesPosted Oct 9, 2014, 7:52 PM
public T this[int index] { get { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException("Index cannot be less than the index of the first element of the deque or greater than the index of the last element in the deque."); if (front.Count - frontDeleted > index) return front[front.Count - 1 - index]; else return back[index - front.Count + frontDeleted]; } }
VulpesPosted Oct 9, 2014, 7:52 PM
There seems to be a problem with your At() method as the first time I tried it with a valid index, I got an index out of range exception. I'd have written it myself as follows using an indexer rather than an At() method as this is more the C# way of doing things.
Paul BrenekPosted Oct 9, 2014, 3:31 PM
I ended up writing a Disposable pattern for the Deque class
Paul BrenekPosted Oct 9, 2014, 2:55 PM
Also, I tried using the Deque class in a using statement. I got errors stating that the Deque class must be implicitly convertible to System.IDisposable. Could you please offer any thoughts on this? It would be greatly appreciated. Thank you.
Paul BrenekPosted Oct 9, 2014, 2:35 PM
public T At(int index) { if (index < frontDeleted || index >= back.Count) throw new ArgumentOutOfRangeException("Index cannot be less than the index of the first element of the deque or greater than the index of the last element in the deque."); // ... initialize the result to be returned to some value; here we pick the first value T result = front[frontDeleted]; if (index >= frontDeleted && index < front.Count) result = front[index]; if (index >= backDeleted && index < back.Count) result = back[index]; return result;}
Paul BrenekPosted Oct 9, 2014, 2:33 PM
Thank you for your reply. I understand the idea of using LINQ but in keeping with the fact that a deque class typically has an At() method, I wrote one. I am including it here. Could you please have a look at it? Hopefully it is OK. Thank you.
VulpesPosted Oct 8, 2014, 5:41 PM
Thanks for the comment Paul. As the Deque class is intended to be analogous to the Queue class in the .NET framework, there's no At() method or indexer.However, as it implements IEnumerable<T>, you can use LINQ's ElementAt() method to achieve the same effect. It's possible to start with an empty Deque by using either the constructor which takes no parameters or the one which sets the initial capacity and passing 0 for that. However, as soon as you add an element, the capacity will increase to 4 as this is the default capacity of the underlying Lists.
Paul BrenekPosted Oct 8, 2014, 3:33 PM
The code you provide is great thank you. I have two small questions though: 1. The code for "at" seems to be missing, and 2. is it possible to start with an empty deque; i.e. no front and back lists initialized? Thank you.
Aaron CronjePosted Nov 29, 2011, 12:12 PM
Its very helpfull article.