diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2016, Joe Canero
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/median-stream.cabal b/median-stream.cabal
new file mode 100644
--- /dev/null
+++ b/median-stream.cabal
@@ -0,0 +1,36 @@
+name:                median-stream
+version:             0.1.0.0
+synopsis:            Constant-time queries for the median of a stream of numeric
+                     data.
+description:         Uses the two-heap approach to support O(lg n) insertions
+                     and O(1) queries for the median.
+homepage:            https://github.com/caneroj1/median-stream#readme
+license:             BSD3
+license-file:        LICENSE
+author:              Joe Canero
+maintainer:          jmc41493@gmail.com
+copyright:           Copyright: (c) 2016 Joe Canero
+category:            Data
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Data.MedianStream
+  build-depends:       base >= 4.7 && < 5
+                     , heap
+  default-language:    Haskell2010
+
+test-suite median-stream-test
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Spec.hs
+  build-depends:       base
+                     , median-stream
+                     , QuickCheck
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/caneroj1/median-stream
diff --git a/src/Data/MedianStream.hs b/src/Data/MedianStream.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/MedianStream.hs
@@ -0,0 +1,91 @@
+{-|
+Module      : Data.MedianStream
+Description : Main module
+Copyright   : (c) Joseph Canero, 2016
+License     : BSD-3
+Maintainer  : jmc41493@gmail.com
+Stability   : experimental
+Portability : POSIX
+-}
+
+{-# LANGUAGE GADTs #-}
+
+module Data.MedianStream
+(
+  MedianStream
+, (+>)
+, (<+)
+, empty
+, insert
+, median
+, size
+) where
+
+import Data.Heap (MaxHeap, MinHeap, Heap)
+import qualified Data.Heap as Heap hiding (MaxHeap, MinHeap, Heap)
+import Data.Maybe (fromJust)
+
+type Left a  = MaxHeap a
+type Right a = MinHeap a
+
+-- | A MedianStream is a data type that can be inserted into and queried
+-- to get a median of a stream of numeric values.
+data MedianStream a where
+  MedianStream :: (Real a, Eq a) => Left a -> Right a -> MedianStream a
+
+-- Infix wrapper around insert with the MedianStream on the left.
+-- Complexity: O(lgn)
+(+>) :: MedianStream a -> a -> MedianStream a
+(+>) ms a = insert a ms
+
+-- Infix wrapper around insert with the MedianStream on the right.
+-- Complexity: O(lgn)
+(<+) :: a -> MedianStream a -> MedianStream a
+(<+) = insert
+
+-- Create an empty MedianStream with no values.
+-- Complexity: O(1)
+empty :: (Real a, Eq a) => MedianStream a
+empty = MedianStream Heap.empty Heap.empty
+
+-- Insert a new numeric value into the median stream.
+-- Complexity: O(lgn)
+insert :: a -> MedianStream a -> MedianStream a
+insert a ms@(MedianStream lh rh)
+  | even $ size ms = oddMedianStream
+  | otherwise      = evenMedianStream
+    where
+      oddMedianStream
+        | maybe False (a >=) $ Heap.viewHead rh =
+          uncurry MedianStream $ popAndSwap lh rh a
+        | otherwise = MedianStream (Heap.insert a lh) rh
+      evenMedianStream
+        | maybe False (a < ) $ Heap.viewHead lh =
+          uncurry (flip MedianStream) $ popAndSwap rh lh a
+        | otherwise = MedianStream lh (Heap.insert a rh)
+
+median :: MedianStream a -> Maybe Double
+median ms@(MedianStream lh rh)
+  | even $ size ms = average <$> Heap.viewHead lh <*> Heap.viewHead rh
+  | otherwise      = (fromRational . toRational) <$> Heap.viewHead lh
+
+-- Returns the number of elements in the MedianStream.
+-- Complexity: O(1)
+size :: MedianStream a -> Int
+size (MedianStream lh rh) = Heap.size lh + Heap.size rh
+
+popAndSwap :: (Heap.HeapItem pol1 a,
+               Heap.HeapItem pol2 a)
+           => Heap pol1 a
+           -> Heap pol2 a
+           -> a
+           -> (Heap pol1 a, Heap pol2 a)
+popAndSwap lh rh a =
+  let
+    ([top], srh) = Heap.splitAt 1 rh
+    nlh          = Heap.insert top lh
+    nrh          = Heap.insert a   srh
+    in (nlh, nrh)
+
+average :: (Real a) => a -> a -> Double
+average x l = (/2 ) . fromRational $ toRational (x + l)
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,42 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+import Data.List hiding (insert)
+import Data.MedianStream
+import Test.QuickCheck
+
+prop_medianStreamOnInts :: [Int] -> Bool
+prop_medianStreamOnInts xs = medianOf xs == median (foldr insert empty xs)
+
+prop_medianStreamOnDoubles :: [Double] -> Bool
+prop_medianStreamOnDoubles xs = medianOf xs == median (foldr insert empty xs)
+
+prop_medianStreamOnIntsV2 :: [Int] -> Bool
+prop_medianStreamOnIntsV2 xs = medianOf xs == median (foldr (<+) empty xs)
+
+prop_medianStreamOnDoublesV2 :: [Double] -> Bool
+prop_medianStreamOnDoublesV2 xs = medianOf xs == median (foldr (<+) empty xs)
+
+prop_medianStreamOnIntsV3 :: [Int] -> Bool
+prop_medianStreamOnIntsV3 xs = medianOf xs == median (foldl' (+>) empty xs)
+
+prop_medianStreamOnDoublesV3 :: [Double] -> Bool
+prop_medianStreamOnDoublesV3 xs = medianOf xs == median (foldl' (+>) empty xs)
+
+medianOf :: (Real a, Eq a) => [a] -> Maybe Double
+medianOf []  = Nothing
+medianOf [x] = Just . fromRational $ toRational x
+medianOf xs  =
+  let sorted = sort xs
+      len    = length xs
+    in case even len of
+      True  -> let [x, y] = take 2 $ drop (len `div` 2 - 1) sorted
+                in Just $ average x y
+      False -> medianOf . take 1 $ drop (len `div` 2) sorted
+
+average :: (Real a) => a -> a -> Double
+average x l = (/2) . fromRational $ toRational (x + l)
+
+return []
+runTests = $quickCheckAll
+
+main = runTests
